├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── flutter
│ │ │ │ └── reply
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ ├── launch_background.xml
│ │ │ └── launch_color.xml
│ │ │ ├── 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
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── 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-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── app.dart
├── bottom_drawer.dart
├── colors.dart
├── compose_page.dart
├── custom_transition_page.dart
├── home.dart
├── inbox.dart
├── mail_card_preview.dart
├── mail_view_page.dart
├── mail_view_router.dart
├── main.dart
├── model
│ ├── email_model.dart
│ ├── email_store.dart
│ └── router_provider.dart
├── profile_avatar.dart
├── router.dart
├── search_page.dart
├── settings_bottom_sheet.dart
└── waterfall_notched_rectangle.dart
├── pubspec.lock
├── pubspec.yaml
├── screenshots
├── reply-transitions-android.gif
└── reply-transitions-iOS.gif
├── test
└── widget_test.dart
└── web
├── favicon.png
├── icons
├── Icon-192.png
└── Icon-512.png
├── index.html
└── manifest.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 | .pub/
4 | .idea
5 | .atom
6 | .vscode
7 | .flutter-plugins
8 | .flutter-plugins-dependencies
9 | .packages
10 | build/
11 | *.iml
12 | **/ephemeral
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
8 | channel: master
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
17 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
18 | - platform: android
19 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
20 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
21 | - platform: ios
22 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
23 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
24 | - platform: linux
25 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
26 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
27 | - platform: macos
28 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
29 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
30 | - platform: web
31 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
32 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
33 | - platform: windows
34 | create_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
35 | base_revision: 07c7d2e454c8126dbe6498c51399c6ca8073f4b6
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Codelab: Building Beautiful Transitions with Material Motion for Flutter
2 |
3 | The Material motion system for Flutter is a set of transition patterns within
4 | the [Animations package](https://pub.dev/packages/animations) that can help
5 | users understand and navigate an app, as described in the
6 | [Material Design guidelines](https://material.io/design/motion/the-motion-system.html).
7 |
8 | This repo houses the source for the
9 | [Material motion system codelab](https://codelabs.developers.google.com/codelabs/material-motion-flutter),
10 | during which you will build Material transitions into an example email app
11 | called Reply.
12 |
13 | The starter code is available on the default `starter` branch, and the complete
14 | code is available on the `complete` branch, which can you can checkout by
15 | running `git checkout complete`.
16 |
17 | | Android | iOS |
18 | |----|----|
19 | | ||
20 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
--------------------------------------------------------------------------------
/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 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/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 flutter.compileSdkVersion
30 | // ndkVersion flutter.ndkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "com.flutter.reply"
48 | // You can update the following values to match your application needs.
49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
50 | minSdkVersion flutter.minSdkVersion
51 | targetSdkVersion flutter.targetSdkVersion
52 | versionCode flutterVersionCode.toInteger()
53 | versionName flutterVersionName
54 | }
55 |
56 | buildTypes {
57 | release {
58 | // TODO: Add your own signing config for the release build.
59 | // Signing with the debug keys for now, so `flutter run --release` works.
60 | signingConfig signingConfigs.debug
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
71 | }
72 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/flutter/reply/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.flutter.reply
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.3'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
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 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/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-7.4-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - path_provider_ios (0.0.1):
4 | - Flutter
5 |
6 | DEPENDENCIES:
7 | - Flutter (from `Flutter`)
8 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
9 |
10 | EXTERNAL SOURCES:
11 | Flutter:
12 | :path: Flutter
13 | path_provider_ios:
14 | :path: ".symlinks/plugins/path_provider_ios/ios"
15 |
16 | SPEC CHECKSUMS:
17 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
18 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
19 |
20 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
21 |
22 | COCOAPODS: 1.11.2
23 |
--------------------------------------------------------------------------------
/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 | C17A20C865AC42F3C8631633 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 151B81D07344E024C42EC367 /* Pods_Runner.framework */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXCopyFilesBuildPhase section */
20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
21 | isa = PBXCopyFilesBuildPhase;
22 | buildActionMask = 2147483647;
23 | dstPath = "";
24 | dstSubfolderSpec = 10;
25 | files = (
26 | );
27 | name = "Embed Frameworks";
28 | runOnlyForDeploymentPostprocessing = 0;
29 | };
30 | /* End PBXCopyFilesBuildPhase section */
31 |
32 | /* Begin PBXFileReference section */
33 | 021D377D0B7C746C102071DB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
34 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
35 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
36 | 151B81D07344E024C42EC367 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
37 | 2FE079A7CCDA80B97BCDE461 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
38 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
39 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
40 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
49 | B0F154022B32EADAB0E7E595 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | C17A20C865AC42F3C8631633 /* Pods_Runner.framework in Frameworks */,
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | /* End PBXFrameworksBuildPhase section */
62 |
63 | /* Begin PBXGroup section */
64 | 30011DC4E50D16F01B73E0ED /* Frameworks */ = {
65 | isa = PBXGroup;
66 | children = (
67 | 151B81D07344E024C42EC367 /* Pods_Runner.framework */,
68 | );
69 | name = Frameworks;
70 | sourceTree = "";
71 | };
72 | 9740EEB11CF90186004384FC /* Flutter */ = {
73 | isa = PBXGroup;
74 | children = (
75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
76 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
77 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
78 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
79 | );
80 | name = Flutter;
81 | sourceTree = "";
82 | };
83 | 97C146E51CF9000F007C117D = {
84 | isa = PBXGroup;
85 | children = (
86 | 9740EEB11CF90186004384FC /* Flutter */,
87 | 97C146F01CF9000F007C117D /* Runner */,
88 | 97C146EF1CF9000F007C117D /* Products */,
89 | C56CAF54698517D229601CA8 /* Pods */,
90 | 30011DC4E50D16F01B73E0ED /* Frameworks */,
91 | );
92 | sourceTree = "";
93 | };
94 | 97C146EF1CF9000F007C117D /* Products */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 97C146EE1CF9000F007C117D /* Runner.app */,
98 | );
99 | name = Products;
100 | sourceTree = "";
101 | };
102 | 97C146F01CF9000F007C117D /* Runner */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
106 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
107 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
108 | 97C147021CF9000F007C117D /* Info.plist */,
109 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
110 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
111 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
112 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
113 | );
114 | path = Runner;
115 | sourceTree = "";
116 | };
117 | C56CAF54698517D229601CA8 /* Pods */ = {
118 | isa = PBXGroup;
119 | children = (
120 | B0F154022B32EADAB0E7E595 /* Pods-Runner.debug.xcconfig */,
121 | 021D377D0B7C746C102071DB /* Pods-Runner.release.xcconfig */,
122 | 2FE079A7CCDA80B97BCDE461 /* Pods-Runner.profile.xcconfig */,
123 | );
124 | name = Pods;
125 | path = Pods;
126 | sourceTree = "";
127 | };
128 | /* End PBXGroup section */
129 |
130 | /* Begin PBXNativeTarget section */
131 | 97C146ED1CF9000F007C117D /* Runner */ = {
132 | isa = PBXNativeTarget;
133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
134 | buildPhases = (
135 | 4756D50EFAC2FE7AA3D4F4AB /* [CP] Check Pods Manifest.lock */,
136 | 9740EEB61CF901F6004384FC /* Run Script */,
137 | 97C146EA1CF9000F007C117D /* Sources */,
138 | 97C146EB1CF9000F007C117D /* Frameworks */,
139 | 97C146EC1CF9000F007C117D /* Resources */,
140 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
142 | 8830A2EE7037C23B92D68813 /* [CP] Embed Pods Frameworks */,
143 | );
144 | buildRules = (
145 | );
146 | dependencies = (
147 | );
148 | name = Runner;
149 | productName = Runner;
150 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
151 | productType = "com.apple.product-type.application";
152 | };
153 | /* End PBXNativeTarget section */
154 |
155 | /* Begin PBXProject section */
156 | 97C146E61CF9000F007C117D /* Project object */ = {
157 | isa = PBXProject;
158 | attributes = {
159 | LastUpgradeCheck = 1300;
160 | ORGANIZATIONNAME = "";
161 | TargetAttributes = {
162 | 97C146ED1CF9000F007C117D = {
163 | CreatedOnToolsVersion = 7.3.1;
164 | LastSwiftMigration = 1100;
165 | };
166 | };
167 | };
168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
169 | compatibilityVersion = "Xcode 9.3";
170 | developmentRegion = en;
171 | hasScannedForEncodings = 0;
172 | knownRegions = (
173 | en,
174 | Base,
175 | );
176 | mainGroup = 97C146E51CF9000F007C117D;
177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
178 | projectDirPath = "";
179 | projectRoot = "";
180 | targets = (
181 | 97C146ED1CF9000F007C117D /* Runner */,
182 | );
183 | };
184 | /* End PBXProject section */
185 |
186 | /* Begin PBXResourcesBuildPhase section */
187 | 97C146EC1CF9000F007C117D /* Resources */ = {
188 | isa = PBXResourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | };
198 | /* End PBXResourcesBuildPhase section */
199 |
200 | /* Begin PBXShellScriptBuildPhase section */
201 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
202 | isa = PBXShellScriptBuildPhase;
203 | buildActionMask = 2147483647;
204 | files = (
205 | );
206 | inputPaths = (
207 | );
208 | name = "Thin Binary";
209 | outputPaths = (
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | shellPath = /bin/sh;
213 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
214 | };
215 | 4756D50EFAC2FE7AA3D4F4AB /* [CP] Check Pods Manifest.lock */ = {
216 | isa = PBXShellScriptBuildPhase;
217 | buildActionMask = 2147483647;
218 | files = (
219 | );
220 | inputFileListPaths = (
221 | );
222 | inputPaths = (
223 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
224 | "${PODS_ROOT}/Manifest.lock",
225 | );
226 | name = "[CP] Check Pods Manifest.lock";
227 | outputFileListPaths = (
228 | );
229 | outputPaths = (
230 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
231 | );
232 | runOnlyForDeploymentPostprocessing = 0;
233 | shellPath = /bin/sh;
234 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
235 | showEnvVarsInLog = 0;
236 | };
237 | 8830A2EE7037C23B92D68813 /* [CP] Embed Pods Frameworks */ = {
238 | isa = PBXShellScriptBuildPhase;
239 | buildActionMask = 2147483647;
240 | files = (
241 | );
242 | inputFileListPaths = (
243 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
244 | );
245 | name = "[CP] Embed Pods Frameworks";
246 | outputFileListPaths = (
247 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | shellPath = /bin/sh;
251 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
252 | showEnvVarsInLog = 0;
253 | };
254 | 9740EEB61CF901F6004384FC /* Run Script */ = {
255 | isa = PBXShellScriptBuildPhase;
256 | buildActionMask = 2147483647;
257 | files = (
258 | );
259 | inputPaths = (
260 | );
261 | name = "Run Script";
262 | outputPaths = (
263 | );
264 | runOnlyForDeploymentPostprocessing = 0;
265 | shellPath = /bin/sh;
266 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
267 | };
268 | /* End PBXShellScriptBuildPhase section */
269 |
270 | /* Begin PBXSourcesBuildPhase section */
271 | 97C146EA1CF9000F007C117D /* Sources */ = {
272 | isa = PBXSourcesBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
276 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
277 | );
278 | runOnlyForDeploymentPostprocessing = 0;
279 | };
280 | /* End PBXSourcesBuildPhase section */
281 |
282 | /* Begin PBXVariantGroup section */
283 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
284 | isa = PBXVariantGroup;
285 | children = (
286 | 97C146FB1CF9000F007C117D /* Base */,
287 | );
288 | name = Main.storyboard;
289 | sourceTree = "";
290 | };
291 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
292 | isa = PBXVariantGroup;
293 | children = (
294 | 97C147001CF9000F007C117D /* Base */,
295 | );
296 | name = LaunchScreen.storyboard;
297 | sourceTree = "";
298 | };
299 | /* End PBXVariantGroup section */
300 |
301 | /* Begin XCBuildConfiguration section */
302 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
303 | isa = XCBuildConfiguration;
304 | buildSettings = {
305 | ALWAYS_SEARCH_USER_PATHS = NO;
306 | CLANG_ANALYZER_NONNULL = YES;
307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
308 | CLANG_CXX_LIBRARY = "libc++";
309 | CLANG_ENABLE_MODULES = YES;
310 | CLANG_ENABLE_OBJC_ARC = YES;
311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
312 | CLANG_WARN_BOOL_CONVERSION = YES;
313 | CLANG_WARN_COMMA = YES;
314 | CLANG_WARN_CONSTANT_CONVERSION = YES;
315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
317 | CLANG_WARN_EMPTY_BODY = YES;
318 | CLANG_WARN_ENUM_CONVERSION = YES;
319 | CLANG_WARN_INFINITE_RECURSION = YES;
320 | CLANG_WARN_INT_CONVERSION = YES;
321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
326 | CLANG_WARN_STRICT_PROTOTYPES = YES;
327 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
328 | CLANG_WARN_UNREACHABLE_CODE = YES;
329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
331 | COPY_PHASE_STRIP = NO;
332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
333 | ENABLE_NS_ASSERTIONS = NO;
334 | ENABLE_STRICT_OBJC_MSGSEND = YES;
335 | GCC_C_LANGUAGE_STANDARD = gnu99;
336 | GCC_NO_COMMON_BLOCKS = YES;
337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
339 | GCC_WARN_UNDECLARED_SELECTOR = YES;
340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
341 | GCC_WARN_UNUSED_FUNCTION = YES;
342 | GCC_WARN_UNUSED_VARIABLE = YES;
343 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
344 | MTL_ENABLE_DEBUG_INFO = NO;
345 | SDKROOT = iphoneos;
346 | SUPPORTED_PLATFORMS = iphoneos;
347 | TARGETED_DEVICE_FAMILY = "1,2";
348 | VALIDATE_PRODUCT = YES;
349 | };
350 | name = Profile;
351 | };
352 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
353 | isa = XCBuildConfiguration;
354 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
355 | buildSettings = {
356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
357 | CLANG_ENABLE_MODULES = YES;
358 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
359 | ENABLE_BITCODE = NO;
360 | FRAMEWORK_SEARCH_PATHS = (
361 | "$(inherited)",
362 | "$(PROJECT_DIR)/Flutter",
363 | );
364 | INFOPLIST_FILE = Runner/Info.plist;
365 | LD_RUNPATH_SEARCH_PATHS = (
366 | "$(inherited)",
367 | "@executable_path/Frameworks",
368 | );
369 | LIBRARY_SEARCH_PATHS = (
370 | "$(inherited)",
371 | "$(PROJECT_DIR)/Flutter",
372 | );
373 | PRODUCT_BUNDLE_IDENTIFIER = com.flutter.reply;
374 | PRODUCT_NAME = "$(TARGET_NAME)";
375 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
376 | SWIFT_VERSION = 5.0;
377 | VERSIONING_SYSTEM = "apple-generic";
378 | };
379 | name = Profile;
380 | };
381 | 97C147031CF9000F007C117D /* Debug */ = {
382 | isa = XCBuildConfiguration;
383 | buildSettings = {
384 | ALWAYS_SEARCH_USER_PATHS = NO;
385 | CLANG_ANALYZER_NONNULL = YES;
386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
387 | CLANG_CXX_LIBRARY = "libc++";
388 | CLANG_ENABLE_MODULES = YES;
389 | CLANG_ENABLE_OBJC_ARC = YES;
390 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
391 | CLANG_WARN_BOOL_CONVERSION = YES;
392 | CLANG_WARN_COMMA = YES;
393 | CLANG_WARN_CONSTANT_CONVERSION = YES;
394 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
396 | CLANG_WARN_EMPTY_BODY = YES;
397 | CLANG_WARN_ENUM_CONVERSION = YES;
398 | CLANG_WARN_INFINITE_RECURSION = YES;
399 | CLANG_WARN_INT_CONVERSION = YES;
400 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
401 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
402 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
403 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
404 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
405 | CLANG_WARN_STRICT_PROTOTYPES = YES;
406 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
407 | CLANG_WARN_UNREACHABLE_CODE = YES;
408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
409 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
410 | COPY_PHASE_STRIP = NO;
411 | DEBUG_INFORMATION_FORMAT = dwarf;
412 | ENABLE_STRICT_OBJC_MSGSEND = YES;
413 | ENABLE_TESTABILITY = YES;
414 | GCC_C_LANGUAGE_STANDARD = gnu99;
415 | GCC_DYNAMIC_NO_PIC = NO;
416 | GCC_NO_COMMON_BLOCKS = YES;
417 | GCC_OPTIMIZATION_LEVEL = 0;
418 | GCC_PREPROCESSOR_DEFINITIONS = (
419 | "DEBUG=1",
420 | "$(inherited)",
421 | );
422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
424 | GCC_WARN_UNDECLARED_SELECTOR = YES;
425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
426 | GCC_WARN_UNUSED_FUNCTION = YES;
427 | GCC_WARN_UNUSED_VARIABLE = YES;
428 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
429 | MTL_ENABLE_DEBUG_INFO = YES;
430 | ONLY_ACTIVE_ARCH = YES;
431 | SDKROOT = iphoneos;
432 | TARGETED_DEVICE_FAMILY = "1,2";
433 | };
434 | name = Debug;
435 | };
436 | 97C147041CF9000F007C117D /* Release */ = {
437 | isa = XCBuildConfiguration;
438 | buildSettings = {
439 | ALWAYS_SEARCH_USER_PATHS = NO;
440 | CLANG_ANALYZER_NONNULL = YES;
441 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
442 | CLANG_CXX_LIBRARY = "libc++";
443 | CLANG_ENABLE_MODULES = YES;
444 | CLANG_ENABLE_OBJC_ARC = YES;
445 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
446 | CLANG_WARN_BOOL_CONVERSION = YES;
447 | CLANG_WARN_COMMA = YES;
448 | CLANG_WARN_CONSTANT_CONVERSION = YES;
449 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
450 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
451 | CLANG_WARN_EMPTY_BODY = YES;
452 | CLANG_WARN_ENUM_CONVERSION = YES;
453 | CLANG_WARN_INFINITE_RECURSION = YES;
454 | CLANG_WARN_INT_CONVERSION = YES;
455 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
456 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
457 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
458 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
459 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
460 | CLANG_WARN_STRICT_PROTOTYPES = YES;
461 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
462 | CLANG_WARN_UNREACHABLE_CODE = YES;
463 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
464 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
465 | COPY_PHASE_STRIP = NO;
466 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
467 | ENABLE_NS_ASSERTIONS = NO;
468 | ENABLE_STRICT_OBJC_MSGSEND = YES;
469 | GCC_C_LANGUAGE_STANDARD = gnu99;
470 | GCC_NO_COMMON_BLOCKS = YES;
471 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
472 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
473 | GCC_WARN_UNDECLARED_SELECTOR = YES;
474 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
475 | GCC_WARN_UNUSED_FUNCTION = YES;
476 | GCC_WARN_UNUSED_VARIABLE = YES;
477 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
478 | MTL_ENABLE_DEBUG_INFO = NO;
479 | SDKROOT = iphoneos;
480 | SUPPORTED_PLATFORMS = iphoneos;
481 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
482 | TARGETED_DEVICE_FAMILY = "1,2";
483 | VALIDATE_PRODUCT = YES;
484 | };
485 | name = Release;
486 | };
487 | 97C147061CF9000F007C117D /* Debug */ = {
488 | isa = XCBuildConfiguration;
489 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
490 | buildSettings = {
491 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
492 | CLANG_ENABLE_MODULES = YES;
493 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
494 | ENABLE_BITCODE = NO;
495 | FRAMEWORK_SEARCH_PATHS = (
496 | "$(inherited)",
497 | "$(PROJECT_DIR)/Flutter",
498 | );
499 | INFOPLIST_FILE = Runner/Info.plist;
500 | LD_RUNPATH_SEARCH_PATHS = (
501 | "$(inherited)",
502 | "@executable_path/Frameworks",
503 | );
504 | LIBRARY_SEARCH_PATHS = (
505 | "$(inherited)",
506 | "$(PROJECT_DIR)/Flutter",
507 | );
508 | PRODUCT_BUNDLE_IDENTIFIER = com.flutter.reply;
509 | PRODUCT_NAME = "$(TARGET_NAME)";
510 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
511 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
512 | SWIFT_VERSION = 5.0;
513 | VERSIONING_SYSTEM = "apple-generic";
514 | };
515 | name = Debug;
516 | };
517 | 97C147071CF9000F007C117D /* Release */ = {
518 | isa = XCBuildConfiguration;
519 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
520 | buildSettings = {
521 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
522 | CLANG_ENABLE_MODULES = YES;
523 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
524 | ENABLE_BITCODE = NO;
525 | FRAMEWORK_SEARCH_PATHS = (
526 | "$(inherited)",
527 | "$(PROJECT_DIR)/Flutter",
528 | );
529 | INFOPLIST_FILE = Runner/Info.plist;
530 | LD_RUNPATH_SEARCH_PATHS = (
531 | "$(inherited)",
532 | "@executable_path/Frameworks",
533 | );
534 | LIBRARY_SEARCH_PATHS = (
535 | "$(inherited)",
536 | "$(PROJECT_DIR)/Flutter",
537 | );
538 | PRODUCT_BUNDLE_IDENTIFIER = com.flutter.reply;
539 | PRODUCT_NAME = "$(TARGET_NAME)";
540 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
541 | SWIFT_VERSION = 5.0;
542 | VERSIONING_SYSTEM = "apple-generic";
543 | };
544 | name = Release;
545 | };
546 | /* End XCBuildConfiguration section */
547 |
548 | /* Begin XCConfigurationList section */
549 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
550 | isa = XCConfigurationList;
551 | buildConfigurations = (
552 | 97C147031CF9000F007C117D /* Debug */,
553 | 97C147041CF9000F007C117D /* Release */,
554 | 249021D3217E4FDB00AE95B9 /* Profile */,
555 | );
556 | defaultConfigurationIsVisible = 0;
557 | defaultConfigurationName = Release;
558 | };
559 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
560 | isa = XCConfigurationList;
561 | buildConfigurations = (
562 | 97C147061CF9000F007C117D /* Debug */,
563 | 97C147071CF9000F007C117D /* Release */,
564 | 249021D4217E4FDB00AE95B9 /* Profile */,
565 | );
566 | defaultConfigurationIsVisible = 0;
567 | defaultConfigurationName = Release;
568 | };
569 | /* End XCConfigurationList section */
570 | };
571 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
572 | }
573 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | Reply
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 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:google_fonts/google_fonts.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:reply/router.dart';
5 |
6 | import 'colors.dart';
7 | import 'model/email_store.dart';
8 | import 'model/router_provider.dart';
9 |
10 | class ReplyApp extends StatefulWidget {
11 | const ReplyApp({Key? key}) : super(key: key);
12 |
13 | @override
14 | ReplyAppState createState() => ReplyAppState();
15 | }
16 |
17 | class ReplyAppState extends State {
18 | final RouterProvider _replyState = RouterProvider(const ReplyHomePath());
19 | final ReplyRouteInformationParser _routeInformationParser =
20 | ReplyRouteInformationParser();
21 | late final ReplyRouterDelegate _routerDelegate;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _routerDelegate = ReplyRouterDelegate(replyState: _replyState);
27 | }
28 |
29 | @override
30 | void dispose() {
31 | _routerDelegate.dispose();
32 | super.dispose();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return MultiProvider(
38 | providers: [
39 | ChangeNotifierProvider.value(value: EmailStore()),
40 | ],
41 | child: Selector(
42 | selector: (context, emailStore) => emailStore.themeMode,
43 | builder: (context, themeMode, child) {
44 | return MaterialApp.router(
45 | routeInformationParser: _routeInformationParser,
46 | routerDelegate: _routerDelegate,
47 | themeMode: themeMode,
48 | title: 'Reply',
49 | darkTheme: _buildReplyDarkTheme(context),
50 | theme: _buildReplyLightTheme(context),
51 | );
52 | }),
53 | );
54 | }
55 | }
56 |
57 | ThemeData _buildReplyLightTheme(BuildContext context) {
58 | final base = ThemeData.light();
59 | return base.copyWith(
60 | bottomSheetTheme: BottomSheetThemeData(
61 | backgroundColor: ReplyColors.blue700,
62 | modalBackgroundColor: Colors.white.withOpacity(0.7),
63 | ),
64 | cardColor: ReplyColors.white50,
65 | chipTheme: _buildChipTheme(
66 | ReplyColors.blue700,
67 | ReplyColors.lightChipBackground,
68 | Brightness.light,
69 | ),
70 | colorScheme: const ColorScheme.light(
71 | primary: ReplyColors.blue700,
72 | secondary: ReplyColors.orange500,
73 | surface: ReplyColors.white50,
74 | error: ReplyColors.red400,
75 | onPrimary: ReplyColors.white50,
76 | onSecondary: ReplyColors.black900,
77 | onBackground: ReplyColors.black900,
78 | onSurface: ReplyColors.black900,
79 | onError: ReplyColors.black900,
80 | background: ReplyColors.blue50,
81 | ),
82 | textTheme: _buildReplyLightTextTheme(base.textTheme),
83 | scaffoldBackgroundColor: ReplyColors.blue50,
84 | bottomAppBarTheme: const BottomAppBarTheme(
85 | color: ReplyColors.blue700,
86 | ),
87 | );
88 | }
89 |
90 | ThemeData _buildReplyDarkTheme(BuildContext context) {
91 | final base = ThemeData.dark();
92 | return base.copyWith(
93 | bottomSheetTheme: BottomSheetThemeData(
94 | backgroundColor: ReplyColors.darkDrawerBackground,
95 | modalBackgroundColor: Colors.black.withOpacity(0.7),
96 | ),
97 | cardColor: ReplyColors.darkCardBackground,
98 | chipTheme: _buildChipTheme(
99 | ReplyColors.blue200,
100 | ReplyColors.darkChipBackground,
101 | Brightness.dark,
102 | ),
103 | colorScheme: const ColorScheme.dark(
104 | primary: ReplyColors.blue200,
105 | secondary: ReplyColors.orange300,
106 | surface: ReplyColors.black800,
107 | error: ReplyColors.red200,
108 | onPrimary: ReplyColors.black900,
109 | onSecondary: ReplyColors.black900,
110 | onBackground: ReplyColors.white50,
111 | onSurface: ReplyColors.white50,
112 | onError: ReplyColors.black900,
113 | background: ReplyColors.black900,
114 | ),
115 | textTheme: _buildReplyDarkTextTheme(base.textTheme),
116 | scaffoldBackgroundColor: ReplyColors.black900,
117 | bottomAppBarTheme: const BottomAppBarTheme(
118 | color: ReplyColors.darkBottomAppBarBackground,
119 | ),
120 | );
121 | }
122 |
123 | ChipThemeData _buildChipTheme(
124 | Color primaryColor,
125 | Color chipBackground,
126 | Brightness brightness,
127 | ) {
128 | return ChipThemeData(
129 | backgroundColor: primaryColor.withOpacity(0.12),
130 | disabledColor: primaryColor.withOpacity(0.87),
131 | selectedColor: primaryColor.withOpacity(0.05),
132 | secondarySelectedColor: chipBackground,
133 | padding: const EdgeInsets.all(4),
134 | shape: const StadiumBorder(),
135 | labelStyle: GoogleFonts.workSansTextTheme().bodyMedium!.copyWith(
136 | color: brightness == Brightness.dark
137 | ? ReplyColors.white50
138 | : ReplyColors.black900,
139 | ),
140 | secondaryLabelStyle: GoogleFonts.workSansTextTheme().bodyMedium!,
141 | brightness: brightness,
142 | );
143 | }
144 |
145 | TextTheme _buildReplyLightTextTheme(TextTheme base) {
146 | return base.copyWith(
147 | headlineMedium: GoogleFonts.workSans(
148 | fontWeight: FontWeight.w600,
149 | fontSize: 34,
150 | letterSpacing: 0.4,
151 | height: 0.9,
152 | color: ReplyColors.black900,
153 | ),
154 | headlineSmall: GoogleFonts.workSans(
155 | fontWeight: FontWeight.bold,
156 | fontSize: 24,
157 | letterSpacing: 0.27,
158 | color: ReplyColors.black900,
159 | ),
160 | titleLarge: GoogleFonts.workSans(
161 | fontWeight: FontWeight.w600,
162 | fontSize: 20,
163 | letterSpacing: 0.18,
164 | color: ReplyColors.black900,
165 | ),
166 | titleSmall: GoogleFonts.workSans(
167 | fontWeight: FontWeight.w600,
168 | fontSize: 14,
169 | letterSpacing: -0.04,
170 | color: ReplyColors.black900,
171 | ),
172 | bodyLarge: GoogleFonts.workSans(
173 | fontWeight: FontWeight.normal,
174 | fontSize: 18,
175 | letterSpacing: 0.2,
176 | color: ReplyColors.black900,
177 | ),
178 | bodyMedium: GoogleFonts.workSans(
179 | fontWeight: FontWeight.normal,
180 | fontSize: 14,
181 | letterSpacing: -0.05,
182 | color: ReplyColors.black900,
183 | ),
184 | bodySmall: GoogleFonts.workSans(
185 | fontWeight: FontWeight.normal,
186 | fontSize: 12,
187 | letterSpacing: 0.2,
188 | color: ReplyColors.black900,
189 | ),
190 | );
191 | }
192 |
193 | TextTheme _buildReplyDarkTextTheme(TextTheme base) {
194 | return base.copyWith(
195 | headlineMedium: GoogleFonts.workSans(
196 | fontWeight: FontWeight.w600,
197 | fontSize: 34,
198 | letterSpacing: 0.4,
199 | height: 0.9,
200 | color: ReplyColors.white50,
201 | ),
202 | headlineSmall: GoogleFonts.workSans(
203 | fontWeight: FontWeight.bold,
204 | fontSize: 24,
205 | letterSpacing: 0.27,
206 | color: ReplyColors.white50,
207 | ),
208 | titleLarge: GoogleFonts.workSans(
209 | fontWeight: FontWeight.w600,
210 | fontSize: 20,
211 | letterSpacing: 0.18,
212 | color: ReplyColors.white50,
213 | ),
214 | titleSmall: GoogleFonts.workSans(
215 | fontWeight: FontWeight.w600,
216 | fontSize: 14,
217 | letterSpacing: -0.04,
218 | color: ReplyColors.white50,
219 | ),
220 | bodyLarge: GoogleFonts.workSans(
221 | fontWeight: FontWeight.normal,
222 | fontSize: 18,
223 | letterSpacing: 0.2,
224 | color: ReplyColors.white50,
225 | ),
226 | bodyMedium: GoogleFonts.workSans(
227 | fontWeight: FontWeight.normal,
228 | fontSize: 14,
229 | letterSpacing: -0.05,
230 | color: ReplyColors.white50,
231 | ),
232 | bodySmall: GoogleFonts.workSans(
233 | fontWeight: FontWeight.normal,
234 | fontSize: 12,
235 | letterSpacing: 0.2,
236 | color: ReplyColors.white50,
237 | ),
238 | );
239 | }
240 |
--------------------------------------------------------------------------------
/lib/bottom_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'colors.dart';
4 |
5 | class BottomDrawer extends StatelessWidget {
6 | const BottomDrawer({
7 | Key? key,
8 | required this.onVerticalDragUpdate,
9 | required this.onVerticalDragEnd,
10 | required this.leading,
11 | required this.trailing,
12 | }) : super(key: key);
13 |
14 | final GestureDragUpdateCallback onVerticalDragUpdate;
15 | final GestureDragEndCallback onVerticalDragEnd;
16 | final Widget leading;
17 | final Widget trailing;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | final theme = Theme.of(context);
22 |
23 | return GestureDetector(
24 | behavior: HitTestBehavior.opaque,
25 | onVerticalDragUpdate: onVerticalDragUpdate,
26 | onVerticalDragEnd: onVerticalDragEnd,
27 | child: Material(
28 | color: theme.bottomSheetTheme.backgroundColor,
29 | borderRadius: const BorderRadius.only(
30 | topLeft: Radius.circular(12),
31 | topRight: Radius.circular(12),
32 | ),
33 | child: ListView(
34 | padding: const EdgeInsets.all(12),
35 | physics: const NeverScrollableScrollPhysics(),
36 | children: [
37 | const SizedBox(height: 28),
38 | leading,
39 | const SizedBox(height: 8),
40 | const Divider(
41 | color: ReplyColors.blue200,
42 | thickness: 0.25,
43 | indent: 18,
44 | endIndent: 160,
45 | ),
46 | const SizedBox(height: 16),
47 | Padding(
48 | padding: const EdgeInsetsDirectional.only(start: 18),
49 | child: Text(
50 | 'FOLDERS',
51 | style: theme.textTheme.bodySmall!.copyWith(
52 | color: ReplyColors.white50.withOpacity(0.64),
53 | ),
54 | ),
55 | ),
56 | const SizedBox(height: 4),
57 | trailing,
58 | ],
59 | ),
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ReplyColors {
4 | static const Color white50 = Color(0xFFFFFFFF);
5 |
6 | static const Color black800 = Color(0xFF121212);
7 | static const Color black900 = Color(0xFF000000);
8 |
9 | static const Color blue50 = Color(0xFFEEF0F2);
10 | static const Color blue100 = Color(0xFFD2DBE0);
11 | static const Color blue200 = Color(0xFFADBBC4);
12 | static const Color blue300 = Color(0xFF8CA2AE);
13 | static const Color blue600 = Color(0xFF4A6572);
14 | static const Color blue700 = Color(0xFF344955);
15 | static const Color blue800 = Color(0xFF232F34);
16 |
17 | static const Color orange300 = Color(0xFFFBD790);
18 | static const Color orange400 = Color(0xFFF9BE64);
19 | static const Color orange500 = Color(0xFFF9AA33);
20 |
21 | static const Color red200 = Color(0xFFCF7779);
22 | static const Color red400 = Color(0xFFFF4C5D);
23 |
24 | static const Color white50Alpha060 = Color(0x99FFFFFF);
25 |
26 | static const Color blue50Alpha060 = Color(0x99EEF0F2);
27 |
28 | static const Color black900Alpha020 = Color(0x33000000);
29 | static const Color black900Alpha087 = Color(0xDE000000);
30 | static const Color black900Alpha060 = Color(0x99000000);
31 |
32 | static const Color greyLabel = Color(0xFFAEAEAE);
33 | static const Color darkBottomAppBarBackground = Color(0xFF2D2D2D);
34 | static const Color darkDrawerBackground = Color(0xFF353535);
35 | static const Color darkCardBackground = Color(0xFF1E1E1E);
36 | static const Color darkChipBackground = Color(0xFF2A2A2A);
37 | static const Color lightChipBackground = Color(0xFFE5E5E5);
38 | }
39 |
--------------------------------------------------------------------------------
/lib/compose_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import 'model/email_store.dart';
5 |
6 | class ComposePage extends StatelessWidget {
7 | const ComposePage({Key? key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | var senderEmail = 'flutterfan@gmail.com';
12 | var subject = '';
13 | var recipient = 'Recipient';
14 | var recipientAvatar = 'reply/avatars/avatar_0.jpg';
15 |
16 | final emailStore = Provider.of(context);
17 |
18 | if (emailStore.currentlySelectedEmailId >= 0) {
19 | final currentEmail = emailStore.emails[emailStore.currentlySelectedInbox]!
20 | .elementAt(emailStore.currentlySelectedEmailId);
21 | subject = currentEmail.subject;
22 | recipient = currentEmail.sender;
23 | recipientAvatar = currentEmail.avatar;
24 | }
25 |
26 | return Scaffold(
27 | body: SafeArea(
28 | bottom: false,
29 | child: SizedBox(
30 | height: double.infinity,
31 | child: Material(
32 | color: Theme.of(context).cardColor,
33 | child: SingleChildScrollView(
34 | child: Column(
35 | crossAxisAlignment: CrossAxisAlignment.start,
36 | mainAxisSize: MainAxisSize.min,
37 | children: [
38 | _SubjectRow(
39 | subject: subject,
40 | ),
41 | const _SectionDivider(),
42 | _SenderAddressRow(
43 | senderEmail: senderEmail,
44 | ),
45 | const _SectionDivider(),
46 | _RecipientsRow(
47 | recipients: recipient,
48 | avatar: recipientAvatar,
49 | ),
50 | const _SectionDivider(),
51 | Padding(
52 | padding: const EdgeInsets.all(12),
53 | child: TextField(
54 | minLines: 6,
55 | maxLines: 20,
56 | decoration: const InputDecoration.collapsed(
57 | hintText: 'New Message...',
58 | ),
59 | autofocus: false,
60 | style: Theme.of(context).textTheme.bodyMedium,
61 | ),
62 | ),
63 | ],
64 | ),
65 | ),
66 | ),
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
73 | class _SubjectRow extends StatefulWidget {
74 | const _SubjectRow({required this.subject});
75 |
76 | final String subject;
77 | @override
78 | _SubjectRowState createState() => _SubjectRowState();
79 | }
80 |
81 | class _SubjectRowState extends State<_SubjectRow> {
82 | late final TextEditingController _subjectController;
83 |
84 | @override
85 | void initState() {
86 | super.initState();
87 | _subjectController = TextEditingController(text: widget.subject);
88 | }
89 |
90 | @override
91 | void dispose() {
92 | _subjectController.dispose();
93 | super.dispose();
94 | }
95 |
96 | @override
97 | Widget build(BuildContext context) {
98 | final theme = Theme.of(context);
99 | final colorScheme = theme.colorScheme;
100 |
101 | return Padding(
102 | padding: const EdgeInsets.only(top: 8),
103 | child: Row(
104 | crossAxisAlignment: CrossAxisAlignment.center,
105 | children: [
106 | IconButton(
107 | onPressed: () => Navigator.of(context).pop(),
108 | icon: Icon(
109 | Icons.close,
110 | color: colorScheme.onSurface,
111 | ),
112 | ),
113 | Expanded(
114 | child: TextField(
115 | controller: _subjectController,
116 | maxLines: 1,
117 | autofocus: false,
118 | style: theme.textTheme.titleLarge,
119 | decoration: InputDecoration.collapsed(
120 | hintText: 'Subject',
121 | hintStyle: theme.textTheme.titleLarge!.copyWith(
122 | color: theme.colorScheme.primary.withOpacity(0.5),
123 | ),
124 | ),
125 | ),
126 | ),
127 | IconButton(
128 | onPressed: () => Navigator.of(context).pop(),
129 | icon: IconButton(
130 | icon: ImageIcon(
131 | const AssetImage(
132 | 'reply/icons/twotone_send.png',
133 | package: 'flutter_gallery_assets',
134 | ),
135 | color: colorScheme.onSurface,
136 | ),
137 | onPressed: () => Navigator.of(context).pop(),
138 | ),
139 | ),
140 | ],
141 | ),
142 | );
143 | }
144 | }
145 |
146 | class _SenderAddressRow extends StatefulWidget {
147 | const _SenderAddressRow({required this.senderEmail});
148 |
149 | final String senderEmail;
150 |
151 | @override
152 | __SenderAddressRowState createState() => __SenderAddressRowState();
153 | }
154 |
155 | class __SenderAddressRowState extends State<_SenderAddressRow> {
156 | late String senderEmail;
157 |
158 | @override
159 | void initState() {
160 | super.initState();
161 | senderEmail = widget.senderEmail;
162 | }
163 |
164 | @override
165 | Widget build(BuildContext context) {
166 | final theme = Theme.of(context);
167 | final textTheme = theme.textTheme;
168 | final accounts = [
169 | 'flutterfan@gmail.com',
170 | 'materialfan@gmail.com',
171 | ];
172 |
173 | return PopupMenuButton(
174 | padding: EdgeInsets.zero,
175 | onSelected: (email) {
176 | setState(() {
177 | senderEmail = email;
178 | });
179 | },
180 | itemBuilder: (context) => >[
181 | PopupMenuItem(
182 | value: accounts[0],
183 | child: Text(
184 | accounts[0],
185 | style: textTheme.bodyMedium,
186 | ),
187 | ),
188 | PopupMenuItem(
189 | value: accounts[1],
190 | child: Text(
191 | accounts[1],
192 | style: textTheme.bodyMedium,
193 | ),
194 | ),
195 | ],
196 | child: Padding(
197 | padding: const EdgeInsets.only(
198 | left: 12,
199 | top: 16,
200 | right: 10,
201 | bottom: 10,
202 | ),
203 | child: Row(
204 | crossAxisAlignment: CrossAxisAlignment.center,
205 | children: [
206 | Expanded(
207 | child: Text(
208 | senderEmail,
209 | style: textTheme.bodyMedium,
210 | ),
211 | ),
212 | Icon(
213 | Icons.arrow_drop_down,
214 | color: theme.colorScheme.onSurface,
215 | ),
216 | ],
217 | ),
218 | ),
219 | );
220 | }
221 | }
222 |
223 | class _RecipientsRow extends StatelessWidget {
224 | const _RecipientsRow({
225 | required this.recipients,
226 | required this.avatar,
227 | });
228 |
229 | final String recipients;
230 | final String avatar;
231 |
232 | @override
233 | Widget build(BuildContext context) {
234 | return Padding(
235 | padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12),
236 | child: Row(
237 | crossAxisAlignment: CrossAxisAlignment.center,
238 | children: [
239 | Expanded(
240 | child: Wrap(
241 | children: [
242 | Chip(
243 | backgroundColor:
244 | Theme.of(context).chipTheme.secondarySelectedColor,
245 | padding: EdgeInsets.zero,
246 | avatar: CircleAvatar(
247 | backgroundImage: AssetImage(
248 | avatar,
249 | package: 'flutter_gallery_assets',
250 | ),
251 | ),
252 | label: Text(
253 | recipients,
254 | ),
255 | ),
256 | ],
257 | ),
258 | ),
259 | InkResponse(
260 | customBorder: const CircleBorder(),
261 | onTap: () {},
262 | radius: 24,
263 | child: const Icon(Icons.add_circle_outline),
264 | ),
265 | ],
266 | ),
267 | );
268 | }
269 | }
270 |
271 | class _SectionDivider extends StatelessWidget {
272 | const _SectionDivider();
273 |
274 | @override
275 | Widget build(BuildContext context) {
276 | return const Divider(
277 | thickness: 1.1,
278 | indent: 10,
279 | endIndent: 10,
280 | );
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/lib/custom_transition_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class CustomTransitionPage extends Page {
4 | final Widget screen;
5 | final ValueKey transitionKey;
6 |
7 | const CustomTransitionPage(
8 | {required this.screen, required this.transitionKey})
9 | : super(key: transitionKey);
10 |
11 | @override
12 | Route createRoute(BuildContext context) {
13 | return PageRouteBuilder(
14 | settings: this,
15 | pageBuilder: (context, animation, secondaryAnimation) {
16 | return screen;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/home.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/rendering.dart';
5 | import 'package:provider/provider.dart';
6 | import 'package:reply/model/router_provider.dart';
7 |
8 | import 'bottom_drawer.dart';
9 | import 'colors.dart';
10 | import 'compose_page.dart';
11 | import 'mail_view_router.dart';
12 | import 'model/email_store.dart';
13 | import 'router.dart';
14 | import 'settings_bottom_sheet.dart';
15 | import 'waterfall_notched_rectangle.dart';
16 |
17 | const _assetsPackage = 'flutter_gallery_assets';
18 | const _iconAssetLocation = 'reply/icons';
19 | const _folderIconAssetLocation = '$_iconAssetLocation/twotone_folder.png';
20 | final mobileMailNavKey = GlobalKey();
21 | const double _kFlingVelocity = 2.0;
22 | const _kAnimationDuration = Duration(milliseconds: 300);
23 |
24 | class HomePage extends StatefulWidget {
25 | const HomePage({Key? key}) : super(key: key);
26 |
27 | @override
28 | HomePageState createState() => HomePageState();
29 | }
30 |
31 | class HomePageState extends State with TickerProviderStateMixin {
32 | late final AnimationController _drawerController;
33 | late final AnimationController _dropArrowController;
34 | late final AnimationController _bottomAppBarController;
35 | late final Animation _drawerCurve;
36 | late final Animation _dropArrowCurve;
37 | late final Animation _bottomAppBarCurve;
38 |
39 | final _bottomDrawerKey = GlobalKey(debugLabel: 'Bottom Drawer');
40 | final _navigationDestinations = const <_Destination>[
41 | _Destination(
42 | name: 'Inbox',
43 | icon: '$_iconAssetLocation/twotone_inbox.png',
44 | index: 0,
45 | ),
46 | _Destination(
47 | name: 'Starred',
48 | icon: '$_iconAssetLocation/twotone_star.png',
49 | index: 1,
50 | ),
51 | _Destination(
52 | name: 'Sent',
53 | icon: '$_iconAssetLocation/twotone_send.png',
54 | index: 2,
55 | ),
56 | _Destination(
57 | name: 'Trash',
58 | icon: '$_iconAssetLocation/twotone_delete.png',
59 | index: 3,
60 | ),
61 | _Destination(
62 | name: 'Spam',
63 | icon: '$_iconAssetLocation/twotone_error.png',
64 | index: 4,
65 | ),
66 | _Destination(
67 | name: 'Drafts',
68 | icon: '$_iconAssetLocation/twotone_drafts.png',
69 | index: 5,
70 | ),
71 | ];
72 |
73 | final _folders = {
74 | 'Receipts': _folderIconAssetLocation,
75 | 'Pine Elementary': _folderIconAssetLocation,
76 | 'Taxes': _folderIconAssetLocation,
77 | 'Vacation': _folderIconAssetLocation,
78 | 'Mortgage': _folderIconAssetLocation,
79 | 'Freelance': _folderIconAssetLocation,
80 | };
81 |
82 | @override
83 | void initState() {
84 | super.initState();
85 |
86 | _drawerController = AnimationController(
87 | duration: _kAnimationDuration,
88 | value: 0,
89 | vsync: this,
90 | )..addListener(() {
91 | if (_drawerController.status == AnimationStatus.dismissed &&
92 | _drawerController.value == 0) {
93 | Provider.of(
94 | context,
95 | listen: false,
96 | ).bottomDrawerVisible = false;
97 | }
98 |
99 | if (_drawerController.value < 0.01) {
100 | setState(() {
101 | //Reload state when drawer is at its smallest to toggle visibility
102 | //If state is reloaded before this drawer closes abruptly instead
103 | //of animating.
104 | });
105 | }
106 | });
107 |
108 | _dropArrowController = AnimationController(
109 | duration: _kAnimationDuration,
110 | vsync: this,
111 | );
112 |
113 | _bottomAppBarController = AnimationController(
114 | vsync: this,
115 | value: 1,
116 | duration: const Duration(milliseconds: 250),
117 | );
118 |
119 | _drawerCurve = CurvedAnimation(
120 | parent: _drawerController,
121 | curve: standardEasing,
122 | reverseCurve: standardEasing.flipped,
123 | );
124 |
125 | _dropArrowCurve = CurvedAnimation(
126 | parent: _dropArrowController,
127 | curve: standardEasing,
128 | reverseCurve: standardEasing.flipped,
129 | );
130 |
131 | _bottomAppBarCurve = CurvedAnimation(
132 | parent: _bottomAppBarController,
133 | curve: standardEasing,
134 | reverseCurve: standardEasing.flipped,
135 | );
136 | }
137 |
138 | @override
139 | void dispose() {
140 | _drawerController.dispose();
141 | _dropArrowController.dispose();
142 | _bottomAppBarController.dispose();
143 | super.dispose();
144 | }
145 |
146 | void _onDestinationSelected(String destination) {
147 | var emailStore = Provider.of(
148 | context,
149 | listen: false,
150 | );
151 |
152 | if (emailStore.onMailView) {
153 | emailStore.currentlySelectedEmailId = -1;
154 | }
155 |
156 | if (emailStore.currentlySelectedInbox != destination) {
157 | emailStore.currentlySelectedInbox = destination;
158 | }
159 |
160 | setState(() {});
161 | }
162 |
163 | bool get _bottomDrawerVisible {
164 | final status = _drawerController.status;
165 | return status == AnimationStatus.completed ||
166 | status == AnimationStatus.forward;
167 | }
168 |
169 | void _toggleBottomDrawerVisibility() {
170 | if (_drawerController.value < 0.4) {
171 | Provider.of(
172 | context,
173 | listen: false,
174 | ).bottomDrawerVisible = true;
175 | _drawerController.animateTo(0.4, curve: standardEasing);
176 | _dropArrowController.animateTo(0.35, curve: standardEasing);
177 | return;
178 | }
179 |
180 | _dropArrowController.forward();
181 | _drawerController.fling(
182 | velocity: _bottomDrawerVisible ? -_kFlingVelocity : _kFlingVelocity,
183 | );
184 | }
185 |
186 | double get _bottomDrawerHeight {
187 | final renderBox =
188 | _bottomDrawerKey.currentContext!.findRenderObject() as RenderBox;
189 | return renderBox.size.height;
190 | }
191 |
192 | void _handleDragUpdate(DragUpdateDetails details) {
193 | _drawerController.value -= details.primaryDelta! / _bottomDrawerHeight;
194 | }
195 |
196 | void _handleDragEnd(DragEndDetails details) {
197 | if (_drawerController.isAnimating ||
198 | _drawerController.status == AnimationStatus.completed) {
199 | return;
200 | }
201 |
202 | final flingVelocity =
203 | details.velocity.pixelsPerSecond.dy / _bottomDrawerHeight;
204 |
205 | if (flingVelocity < 0.0) {
206 | _drawerController.fling(
207 | velocity: math.max(_kFlingVelocity, -flingVelocity),
208 | );
209 | } else if (flingVelocity > 0.0) {
210 | _dropArrowController.forward();
211 | _drawerController.fling(
212 | velocity: math.min(-_kFlingVelocity, -flingVelocity),
213 | );
214 | } else {
215 | if (_drawerController.value < 0.6) {
216 | _dropArrowController.forward();
217 | }
218 | _drawerController.fling(
219 | velocity:
220 | _drawerController.value < 0.6 ? -_kFlingVelocity : _kFlingVelocity,
221 | );
222 | }
223 | }
224 |
225 | bool _handleScrollNotification(ScrollNotification notification) {
226 | if (notification.depth == 0) {
227 | if (notification is UserScrollNotification) {
228 | switch (notification.direction) {
229 | case ScrollDirection.forward:
230 | _bottomAppBarController.forward();
231 | break;
232 | case ScrollDirection.reverse:
233 | _bottomAppBarController.reverse();
234 | break;
235 | case ScrollDirection.idle:
236 | break;
237 | }
238 | }
239 | }
240 | return false;
241 | }
242 |
243 | Widget _buildStack(BuildContext context, BoxConstraints constraints) {
244 | final drawerSize = constraints.biggest;
245 | final drawerTop = drawerSize.height;
246 | final ValueChanged updateMailbox = _onDestinationSelected;
247 |
248 | final drawerAnimation = RelativeRectTween(
249 | begin: RelativeRect.fromLTRB(0.0, drawerTop, 0.0, 0.0),
250 | end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
251 | ).animate(_drawerCurve);
252 |
253 | return Stack(
254 | clipBehavior: Clip.none,
255 | key: _bottomDrawerKey,
256 | children: [
257 | NotificationListener(
258 | onNotification: _handleScrollNotification,
259 | child: _MailRouter(
260 | drawerController: _drawerController,
261 | ),
262 | ),
263 | GestureDetector(
264 | onTap: () {
265 | _drawerController.reverse();
266 | _dropArrowController.reverse();
267 | },
268 | child: Visibility(
269 | maintainAnimation: true,
270 | maintainState: true,
271 | visible: _bottomDrawerVisible,
272 | child: FadeTransition(
273 | opacity: _drawerCurve,
274 | child: Container(
275 | height: MediaQuery.of(context).size.height,
276 | width: MediaQuery.of(context).size.width,
277 | color: Theme.of(context).bottomSheetTheme.modalBackgroundColor,
278 | ),
279 | ),
280 | ),
281 | ),
282 | PositionedTransition(
283 | rect: drawerAnimation,
284 | child: Visibility(
285 | visible: _bottomDrawerVisible,
286 | child: BottomDrawer(
287 | onVerticalDragUpdate: _handleDragUpdate,
288 | onVerticalDragEnd: _handleDragEnd,
289 | leading: _BottomDrawerDestinations(
290 | destinations: _navigationDestinations,
291 | drawerController: _drawerController,
292 | dropArrowController: _dropArrowController,
293 | onItemTapped: updateMailbox,
294 | ),
295 | trailing: _BottomDrawerFolderSection(folders: _folders),
296 | ),
297 | ),
298 | ),
299 | ],
300 | );
301 | }
302 |
303 | @override
304 | Widget build(BuildContext context) {
305 | return Scaffold(
306 | extendBody: true,
307 | body: LayoutBuilder(
308 | builder: _buildStack,
309 | ),
310 | bottomNavigationBar: _AnimatedBottomAppBar(
311 | bottomAppBarController: _bottomAppBarController,
312 | bottomAppBarCurve: _bottomAppBarCurve,
313 | bottomDrawerVisible: _bottomDrawerVisible,
314 | drawerController: _drawerController,
315 | dropArrowCurve: _dropArrowCurve,
316 | toggleBottomDrawerVisibility: _toggleBottomDrawerVisibility,
317 | ),
318 | floatingActionButton: _bottomDrawerVisible
319 | ? null
320 | : const Padding(
321 | padding: EdgeInsetsDirectional.only(bottom: 8),
322 | child: _ReplyFab(),
323 | ),
324 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
325 | );
326 | }
327 | }
328 |
329 | class _AnimatedBottomAppBar extends StatelessWidget {
330 | const _AnimatedBottomAppBar({
331 | required this.bottomAppBarController,
332 | required this.bottomAppBarCurve,
333 | required this.bottomDrawerVisible,
334 | required this.drawerController,
335 | required this.dropArrowCurve,
336 | required this.toggleBottomDrawerVisibility,
337 | });
338 |
339 | final AnimationController bottomAppBarController;
340 | final Animation bottomAppBarCurve;
341 | final bool bottomDrawerVisible;
342 | final AnimationController drawerController;
343 | final Animation dropArrowCurve;
344 | final VoidCallback toggleBottomDrawerVisibility;
345 |
346 | @override
347 | Widget build(BuildContext context) {
348 | var fadeOut = Tween(begin: 1, end: -1).animate(
349 | drawerController.drive(CurveTween(curve: standardEasing)),
350 | );
351 |
352 | return Selector(
353 | selector: (context, emailStore) => emailStore.onMailView,
354 | builder: (context, onMailView, child) {
355 | bottomAppBarController.forward();
356 |
357 | return SizeTransition(
358 | sizeFactor: bottomAppBarCurve,
359 | axisAlignment: -1,
360 | child: Padding(
361 | padding: const EdgeInsetsDirectional.only(top: 2),
362 | child: BottomAppBar(
363 | shape: const WaterfallNotchedRectangle(),
364 | notchMargin: 6,
365 | child: Container(
366 | color: Colors.transparent,
367 | height: kToolbarHeight,
368 | child: Row(
369 | mainAxisSize: MainAxisSize.max,
370 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
371 | children: [
372 | InkWell(
373 | borderRadius: const BorderRadius.all(Radius.circular(16)),
374 | onTap: toggleBottomDrawerVisibility,
375 | child: Row(
376 | children: [
377 | const SizedBox(width: 16),
378 | RotationTransition(
379 | turns: Tween(
380 | begin: 0.0,
381 | end: 1.0,
382 | ).animate(dropArrowCurve),
383 | child: const Icon(
384 | Icons.arrow_drop_up,
385 | color: ReplyColors.white50,
386 | ),
387 | ),
388 | const SizedBox(width: 8),
389 | const _ReplyLogo(),
390 | const SizedBox(width: 10),
391 | // TODO: Add Fade through transition between disappearing mailbox title (Motion)
392 | onMailView
393 | ? const SizedBox(width: 48)
394 | : FadeTransition(
395 | opacity: fadeOut,
396 | child: Selector(
397 | selector: (context, emailStore) =>
398 | emailStore.currentlySelectedInbox,
399 | builder: (
400 | context,
401 | currentlySelectedInbox,
402 | child,
403 | ) {
404 | return Text(
405 | currentlySelectedInbox,
406 | style: Theme.of(context)
407 | .textTheme
408 | .bodyLarge!
409 | .copyWith(
410 | color: ReplyColors.white50,
411 | ),
412 | );
413 | },
414 | ),
415 | ),
416 | ],
417 | ),
418 | ),
419 | Expanded(
420 | child: Container(
421 | color: Colors.transparent,
422 | child: _BottomAppBarActionItems(
423 | drawerController: drawerController,
424 | drawerVisible: bottomDrawerVisible,
425 | ),
426 | ),
427 | ),
428 | ],
429 | ),
430 | ),
431 | ),
432 | ),
433 | );
434 | },
435 | );
436 | }
437 | }
438 |
439 | class _BottomAppBarActionItems extends StatelessWidget {
440 | const _BottomAppBarActionItems({
441 | required this.drawerController,
442 | required this.drawerVisible,
443 | });
444 |
445 | final AnimationController drawerController;
446 | final bool drawerVisible;
447 |
448 | @override
449 | Widget build(BuildContext context) {
450 | return Consumer(
451 | builder: (context, model, child) {
452 | final onMailView = model.onMailView;
453 | var radius = const Radius.circular(12);
454 | final modalBorder = BorderRadius.only(
455 | topRight: radius,
456 | topLeft: radius,
457 | );
458 | Color? starIconColor;
459 |
460 | if (onMailView) {
461 | var currentEmailStarred = false;
462 |
463 | if (model.emails[model.currentlySelectedInbox]!.isNotEmpty) {
464 | currentEmailStarred = model.isEmailStarred(
465 | model.emails[model.currentlySelectedInbox]!
466 | .elementAt(model.currentlySelectedEmailId),
467 | );
468 | }
469 |
470 | starIconColor = currentEmailStarred
471 | ? Theme.of(context).colorScheme.secondary
472 | : ReplyColors.white50;
473 | }
474 |
475 | // TODO: Add Fade through transition between bottom app bar actions (Motion)
476 | return drawerVisible
477 | ? Align(
478 | alignment: AlignmentDirectional.bottomEnd,
479 | child: IconButton(
480 | icon: const Icon(Icons.settings),
481 | color: ReplyColors.white50,
482 | onPressed: () async {
483 | drawerController.reverse();
484 | showModalBottomSheet(
485 | context: context,
486 | shape: RoundedRectangleBorder(
487 | borderRadius: modalBorder,
488 | ),
489 | builder: (context) => const SettingsBottomSheet(),
490 | );
491 | },
492 | ),
493 | )
494 | : onMailView
495 | ? Row(
496 | mainAxisSize: MainAxisSize.max,
497 | mainAxisAlignment: MainAxisAlignment.end,
498 | children: [
499 | IconButton(
500 | icon: ImageIcon(
501 | const AssetImage(
502 | '$_iconAssetLocation/twotone_star.png',
503 | package: _assetsPackage,
504 | ),
505 | color: starIconColor,
506 | ),
507 | onPressed: () {
508 | model.starEmail(
509 | model.currentlySelectedInbox,
510 | model.currentlySelectedEmailId,
511 | );
512 | if (model.currentlySelectedInbox == 'Starred') {
513 | mobileMailNavKey.currentState!.pop();
514 | model.currentlySelectedEmailId = -1;
515 | }
516 | },
517 | color: ReplyColors.white50,
518 | ),
519 | IconButton(
520 | icon: const ImageIcon(
521 | AssetImage(
522 | '$_iconAssetLocation/twotone_delete.png',
523 | package: _assetsPackage,
524 | ),
525 | ),
526 | onPressed: () {
527 | model.deleteEmail(
528 | model.currentlySelectedInbox,
529 | model.currentlySelectedEmailId,
530 | );
531 |
532 | mobileMailNavKey.currentState!.pop();
533 | model.currentlySelectedEmailId = -1;
534 | },
535 | color: ReplyColors.white50,
536 | ),
537 | IconButton(
538 | icon: const Icon(Icons.more_vert),
539 | onPressed: () {},
540 | color: ReplyColors.white50,
541 | ),
542 | ],
543 | )
544 | : Align(
545 | alignment: AlignmentDirectional.bottomEnd,
546 | child: IconButton(
547 | icon: const Icon(Icons.search),
548 | color: ReplyColors.white50,
549 | onPressed: () {
550 | Provider.of(
551 | context,
552 | listen: false,
553 | ).routePath = const ReplySearchPath();
554 | },
555 | ),
556 | );
557 | },
558 | );
559 | }
560 | }
561 |
562 | class _BottomDrawerDestinations extends StatelessWidget {
563 | const _BottomDrawerDestinations({
564 | required this.destinations,
565 | required this.drawerController,
566 | required this.dropArrowController,
567 | required this.onItemTapped,
568 | });
569 |
570 | final List<_Destination> destinations;
571 | final AnimationController drawerController;
572 | final AnimationController dropArrowController;
573 | final ValueChanged onItemTapped;
574 |
575 | @override
576 | Widget build(BuildContext context) {
577 | final theme = Theme.of(context);
578 |
579 | return Column(
580 | children: [
581 | for (var destination in destinations)
582 | InkWell(
583 | onTap: () {
584 | onItemTapped(destination.name);
585 | drawerController.reverse();
586 | dropArrowController.forward();
587 | },
588 | child: Selector(
589 | selector: (context, emailStore) =>
590 | emailStore.currentlySelectedInbox,
591 | builder: (context, currentlySelectedInbox, child) {
592 | return ListTile(
593 | leading: ImageIcon(
594 | AssetImage(
595 | destination.icon,
596 | package: _assetsPackage,
597 | ),
598 | color: destination.name == currentlySelectedInbox
599 | ? theme.colorScheme.secondary
600 | : ReplyColors.white50.withOpacity(0.64),
601 | ),
602 | title: Text(
603 | destination.name,
604 | style: theme.textTheme.bodyMedium!.copyWith(
605 | color: destination.name == currentlySelectedInbox
606 | ? theme.colorScheme.secondary
607 | : ReplyColors.white50.withOpacity(0.64),
608 | ),
609 | ),
610 | );
611 | },
612 | ),
613 | ),
614 | ],
615 | );
616 | }
617 | }
618 |
619 | class _Destination {
620 | const _Destination({
621 | required this.name,
622 | required this.icon,
623 | required this.index,
624 | });
625 |
626 | final String name;
627 | final String icon;
628 | final int index;
629 | }
630 |
631 | class _BottomDrawerFolderSection extends StatelessWidget {
632 | const _BottomDrawerFolderSection({required this.folders});
633 |
634 | final Map folders;
635 |
636 | @override
637 | Widget build(BuildContext context) {
638 | final theme = Theme.of(context);
639 |
640 | return Column(
641 | children: [
642 | for (var folder in folders.keys)
643 | InkWell(
644 | onTap: () {},
645 | child: ListTile(
646 | leading: ImageIcon(
647 | AssetImage(
648 | folders[folder]!,
649 | package: _assetsPackage,
650 | ),
651 | color: ReplyColors.white50.withOpacity(0.64),
652 | ),
653 | title: Text(
654 | folder,
655 | style: theme.textTheme.bodyMedium!.copyWith(
656 | color: ReplyColors.white50.withOpacity(0.64),
657 | ),
658 | ),
659 | ),
660 | ),
661 | ],
662 | );
663 | }
664 | }
665 |
666 | class _MailRouter extends StatelessWidget {
667 | const _MailRouter({required this.drawerController});
668 |
669 | final AnimationController drawerController;
670 |
671 | @override
672 | Widget build(BuildContext context) {
673 | final RootBackButtonDispatcher backButtonDispatcher =
674 | Router.of(context).backButtonDispatcher as RootBackButtonDispatcher;
675 |
676 | return Router(
677 | routerDelegate:
678 | MailViewRouterDelegate(drawerController: drawerController),
679 | backButtonDispatcher: ChildBackButtonDispatcher(backButtonDispatcher)
680 | ..takePriority(),
681 | );
682 | }
683 | }
684 |
685 | class _ReplyLogo extends StatelessWidget {
686 | const _ReplyLogo({Key? key}) : super(key: key);
687 |
688 | @override
689 | Widget build(BuildContext context) {
690 | return const ImageIcon(
691 | AssetImage(
692 | 'reply/reply_logo.png',
693 | package: _assetsPackage,
694 | ),
695 | size: 32,
696 | color: ReplyColors.white50,
697 | );
698 | }
699 | }
700 |
701 | class _ReplyFab extends StatefulWidget {
702 | const _ReplyFab();
703 |
704 | @override
705 | _ReplyFabState createState() => _ReplyFabState();
706 | }
707 |
708 | class _ReplyFabState extends State<_ReplyFab>
709 | with SingleTickerProviderStateMixin {
710 | // TODO: Add Fade through transition between compose and reply FAB (Motion)
711 | static const double _mobileFabDimension = 56;
712 |
713 | @override
714 | Widget build(BuildContext context) {
715 | final theme = Theme.of(context);
716 | const circleFabBorder = CircleBorder();
717 |
718 | return Selector(
719 | selector: (context, emailStore) => emailStore.onMailView,
720 | builder: (context, onMailView, child) {
721 | // TODO: Add Fade through transition between compose and reply FAB (Motion)
722 | final fabSwitcher = onMailView
723 | ? const Icon(
724 | Icons.reply_all,
725 | color: Colors.black,
726 | )
727 | : const Icon(
728 | Icons.create,
729 | color: Colors.black,
730 | );
731 | final tooltip = onMailView ? 'Reply' : 'Compose';
732 |
733 | // TODO: Add Container Transform from FAB to compose email page (Motion)
734 | return Material(
735 | color: theme.colorScheme.secondary,
736 | shape: circleFabBorder,
737 | child: Tooltip(
738 | message: tooltip,
739 | child: InkWell(
740 | customBorder: circleFabBorder,
741 | onTap: () {
742 | Provider.of(
743 | context,
744 | listen: false,
745 | ).onCompose = true;
746 |
747 | Navigator.of(context).push(
748 | PageRouteBuilder(
749 | pageBuilder: (
750 | BuildContext context,
751 | Animation animation,
752 | Animation secondaryAnimation,
753 | ) {
754 | return const ComposePage();
755 | },
756 | ),
757 | );
758 | },
759 | child: SizedBox(
760 | height: _mobileFabDimension,
761 | width: _mobileFabDimension,
762 | child: Center(
763 | child: fabSwitcher,
764 | ),
765 | ),
766 | ),
767 | ),
768 | );
769 | },
770 | );
771 | }
772 | }
773 |
774 | // TODO: Add Fade through transition between compose and reply FAB (Motion)
775 |
--------------------------------------------------------------------------------
/lib/inbox.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import 'mail_card_preview.dart';
5 | import 'model/email_store.dart';
6 |
7 | class InboxPage extends StatelessWidget {
8 | const InboxPage({required this.destination, Key? key}) : super(key: key);
9 |
10 | final String destination;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | const horizontalPadding = 4.0;
15 |
16 | return Consumer(
17 | builder: (context, model, child) {
18 | return SafeArea(
19 | bottom: false,
20 | child: model.emails[destination]!.isEmpty
21 | ? Center(
22 | child: Text(
23 | 'Empty in ${destination.toLowerCase()}',
24 | ),
25 | )
26 | : ListView.separated(
27 | itemCount: model.emails[destination]!.length,
28 | padding: const EdgeInsetsDirectional.only(
29 | start: horizontalPadding,
30 | end: horizontalPadding,
31 | bottom: kToolbarHeight,
32 | ),
33 | primary: false,
34 | separatorBuilder: (context, index) =>
35 | const SizedBox(height: 4),
36 | itemBuilder: (context, index) {
37 | return MailPreviewCard(
38 | id: index,
39 | email: model.emails[destination]!.elementAt(index),
40 | onDelete: () => model.deleteEmail(destination, index),
41 | onStar: () => model.starEmail(destination, index),
42 | );
43 | },
44 | ),
45 | );
46 | },
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/mail_card_preview.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import 'colors.dart';
5 | import 'home.dart';
6 | import 'mail_view_page.dart';
7 | import 'model/email_model.dart';
8 | import 'model/email_store.dart';
9 | import 'profile_avatar.dart';
10 |
11 | class MailPreviewCard extends StatelessWidget {
12 | const MailPreviewCard({
13 | Key? key,
14 | required this.id,
15 | required this.email,
16 | required this.onDelete,
17 | required this.onStar,
18 | }) : super(key: key);
19 |
20 | final int id;
21 | final Email email;
22 | final VoidCallback onDelete;
23 | final VoidCallback onStar;
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | final theme = Theme.of(context);
28 |
29 | final currentEmailStarred = Provider.of(
30 | context,
31 | listen: false,
32 | ).isEmailStarred(email);
33 |
34 | final colorScheme = theme.colorScheme;
35 | final mailPreview = _MailPreview(
36 | id: id,
37 | email: email,
38 | onStar: onStar,
39 | onDelete: onDelete,
40 | );
41 | final onStarredInbox = Provider.of(
42 | context,
43 | listen: false,
44 | ).currentlySelectedInbox ==
45 | 'Starred';
46 |
47 | // TODO: Add Container Transform transition from email list to email detail page (Motion)
48 | return Material(
49 | color: theme.cardColor,
50 | child: InkWell(
51 | onTap: () {
52 | Provider.of(
53 | context,
54 | listen: false,
55 | ).currentlySelectedEmailId = id;
56 |
57 | mobileMailNavKey.currentState!.push(
58 | PageRouteBuilder(
59 | pageBuilder: (BuildContext context, Animation animation,
60 | Animation secondaryAnimation) {
61 | return MailViewPage(id: id, email: email);
62 | },
63 | ),
64 | );
65 | },
66 | child: Dismissible(
67 | key: ObjectKey(email),
68 | dismissThresholds: const {
69 | DismissDirection.startToEnd: 0.8,
70 | DismissDirection.endToStart: 0.4,
71 | },
72 | onDismissed: (direction) {
73 | switch (direction) {
74 | case DismissDirection.endToStart:
75 | if (onStarredInbox) {
76 | onStar();
77 | }
78 | break;
79 | case DismissDirection.startToEnd:
80 | onDelete();
81 | break;
82 | default:
83 | }
84 | },
85 | background: _DismissibleContainer(
86 | icon: 'twotone_delete',
87 | backgroundColor: colorScheme.primary,
88 | iconColor: ReplyColors.blue50,
89 | alignment: Alignment.centerLeft,
90 | padding: const EdgeInsetsDirectional.only(start: 20),
91 | ),
92 | confirmDismiss: (direction) async {
93 | if (direction == DismissDirection.endToStart) {
94 | if (onStarredInbox) {
95 | return true;
96 | }
97 | onStar();
98 | return false;
99 | } else {
100 | return true;
101 | }
102 | },
103 | secondaryBackground: _DismissibleContainer(
104 | icon: 'twotone_star',
105 | backgroundColor: currentEmailStarred
106 | ? colorScheme.secondary
107 | : theme.scaffoldBackgroundColor,
108 | iconColor: currentEmailStarred
109 | ? colorScheme.onSecondary
110 | : colorScheme.onBackground,
111 | alignment: Alignment.centerRight,
112 | padding: const EdgeInsetsDirectional.only(end: 20),
113 | ),
114 | child: mailPreview,
115 | ),
116 | ),
117 | );
118 | }
119 | }
120 |
121 | // TODO: Add Container Transform transition from email list to email detail page (Motion)
122 |
123 | class _DismissibleContainer extends StatelessWidget {
124 | const _DismissibleContainer({
125 | required this.icon,
126 | required this.backgroundColor,
127 | required this.iconColor,
128 | required this.alignment,
129 | required this.padding,
130 | });
131 |
132 | final String icon;
133 | final Color backgroundColor;
134 | final Color iconColor;
135 | final Alignment alignment;
136 | final EdgeInsetsDirectional padding;
137 |
138 | @override
139 | Widget build(BuildContext context) {
140 | return AnimatedContainer(
141 | alignment: alignment,
142 | color: backgroundColor,
143 | curve: standardEasing,
144 | duration: kThemeAnimationDuration,
145 | padding: padding,
146 | child: Material(
147 | color: Colors.transparent,
148 | child: ImageIcon(
149 | AssetImage(
150 | 'reply/icons/$icon.png',
151 | package: 'flutter_gallery_assets',
152 | ),
153 | size: 36,
154 | color: iconColor,
155 | ),
156 | ),
157 | );
158 | }
159 | }
160 |
161 | class _MailPreview extends StatelessWidget {
162 | const _MailPreview({
163 | required this.id,
164 | required this.email,
165 | this.onStar,
166 | this.onDelete,
167 | });
168 |
169 | final int id;
170 | final Email email;
171 | final VoidCallback? onStar;
172 | final VoidCallback? onDelete;
173 |
174 | @override
175 | Widget build(BuildContext context) {
176 | final textTheme = Theme.of(context).textTheme;
177 | var emailStore = Provider.of(
178 | context,
179 | listen: false,
180 | );
181 |
182 | return LayoutBuilder(
183 | builder: (context, constraints) {
184 | return ConstrainedBox(
185 | constraints: BoxConstraints(maxHeight: constraints.maxHeight),
186 | child: Padding(
187 | padding: const EdgeInsets.all(20),
188 | child: Column(
189 | crossAxisAlignment: CrossAxisAlignment.start,
190 | mainAxisSize: MainAxisSize.min,
191 | children: [
192 | Row(
193 | mainAxisSize: MainAxisSize.max,
194 | crossAxisAlignment: CrossAxisAlignment.start,
195 | children: [
196 | Expanded(
197 | child: Column(
198 | crossAxisAlignment: CrossAxisAlignment.start,
199 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
200 | children: [
201 | Text(
202 | '${email.sender} - ${email.time}',
203 | style: textTheme.bodySmall,
204 | ),
205 | const SizedBox(height: 4),
206 | Text(email.subject, style: textTheme.headlineSmall),
207 | const SizedBox(height: 16),
208 | ],
209 | ),
210 | ),
211 | _MailPreviewActionBar(
212 | avatar: email.avatar,
213 | isStarred: emailStore.isEmailStarred(email),
214 | onStar: onStar,
215 | onDelete: onDelete,
216 | ),
217 | ],
218 | ),
219 | Padding(
220 | padding: const EdgeInsetsDirectional.only(
221 | end: 20,
222 | ),
223 | child: Text(
224 | email.message,
225 | overflow: TextOverflow.ellipsis,
226 | maxLines: 1,
227 | style: textTheme.bodyMedium,
228 | ),
229 | ),
230 | if (email.containsPictures) ...[
231 | Flexible(
232 | fit: FlexFit.loose,
233 | child: Column(
234 | children: const [
235 | SizedBox(height: 20),
236 | _PicturePreview(),
237 | ],
238 | ),
239 | ),
240 | ],
241 | ],
242 | ),
243 | ),
244 | );
245 | },
246 | );
247 | }
248 | }
249 |
250 | class _PicturePreview extends StatelessWidget {
251 | const _PicturePreview();
252 |
253 | @override
254 | Widget build(BuildContext context) {
255 | return SizedBox(
256 | height: 96,
257 | child: ListView.builder(
258 | itemCount: 4,
259 | scrollDirection: Axis.horizontal,
260 | itemBuilder: (context, index) {
261 | return Padding(
262 | padding: const EdgeInsetsDirectional.only(end: 4),
263 | child: Image.asset(
264 | 'reply/attachments/paris_${index + 1}.jpg',
265 | gaplessPlayback: true,
266 | package: 'flutter_gallery_assets',
267 | ),
268 | );
269 | },
270 | ),
271 | );
272 | }
273 | }
274 |
275 | class _MailPreviewActionBar extends StatelessWidget {
276 | const _MailPreviewActionBar({
277 | required this.avatar,
278 | required this.isStarred,
279 | this.onStar,
280 | this.onDelete,
281 | });
282 |
283 | final String avatar;
284 | final bool isStarred;
285 | final VoidCallback? onStar;
286 | final VoidCallback? onDelete;
287 |
288 | @override
289 | Widget build(BuildContext context) {
290 | return Row(
291 | children: [
292 | ProfileAvatar(avatar: avatar),
293 | ],
294 | );
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/lib/mail_view_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import 'model/email_model.dart';
5 | import 'model/email_store.dart';
6 | import 'profile_avatar.dart';
7 |
8 | class MailViewPage extends StatelessWidget {
9 | const MailViewPage({Key? key, required this.id, required this.email})
10 | : super(key: key);
11 |
12 | final int id;
13 | final Email email;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Scaffold(
18 | body: SafeArea(
19 | bottom: false,
20 | child: SizedBox(
21 | height: double.infinity,
22 | child: Material(
23 | color: Theme.of(context).cardColor,
24 | child: SingleChildScrollView(
25 | padding: const EdgeInsetsDirectional.only(
26 | top: 42,
27 | start: 20,
28 | end: 20,
29 | ),
30 | child: Column(
31 | crossAxisAlignment: CrossAxisAlignment.start,
32 | children: [
33 | _MailViewHeader(email: email),
34 | const SizedBox(height: 32),
35 | _MailViewBody(message: email.message),
36 | if (email.containsPictures) ...[
37 | const SizedBox(height: 28),
38 | const _PictureGrid(),
39 | ],
40 | const SizedBox(height: kToolbarHeight),
41 | ],
42 | ),
43 | ),
44 | ),
45 | ),
46 | ),
47 | );
48 | }
49 | }
50 |
51 | class _MailViewHeader extends StatelessWidget {
52 | const _MailViewHeader({
53 | required this.email,
54 | });
55 |
56 | final Email email;
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | final textTheme = Theme.of(context).textTheme;
61 |
62 | return Column(
63 | children: [
64 | Row(
65 | crossAxisAlignment: CrossAxisAlignment.start,
66 | children: [
67 | Expanded(
68 | child: Text(
69 | email.subject,
70 | style: textTheme.headlineMedium!.copyWith(height: 1.1),
71 | ),
72 | ),
73 | IconButton(
74 | icon: const Icon(Icons.keyboard_arrow_down),
75 | onPressed: () {
76 | Provider.of(
77 | context,
78 | listen: false,
79 | ).currentlySelectedEmailId = -1;
80 | Navigator.pop(context);
81 | },
82 | splashRadius: 20,
83 | ),
84 | ],
85 | ),
86 | const SizedBox(height: 16),
87 | Row(
88 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
89 | children: [
90 | Column(
91 | crossAxisAlignment: CrossAxisAlignment.start,
92 | mainAxisSize: MainAxisSize.min,
93 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
94 | children: [
95 | Text('${email.sender} - ${email.time}'),
96 | const SizedBox(height: 4),
97 | Text(
98 | 'To ${email.recipients},',
99 | style: textTheme.bodySmall!.copyWith(
100 | color: Theme.of(context)
101 | .colorScheme
102 | .onSurface
103 | .withOpacity(0.64),
104 | ),
105 | ),
106 | ],
107 | ),
108 | Padding(
109 | padding: const EdgeInsetsDirectional.only(end: 4),
110 | child: ProfileAvatar(avatar: email.avatar),
111 | ),
112 | ],
113 | ),
114 | ],
115 | );
116 | }
117 | }
118 |
119 | class _MailViewBody extends StatelessWidget {
120 | const _MailViewBody({required this.message});
121 |
122 | final String message;
123 |
124 | @override
125 | Widget build(BuildContext context) {
126 | return Text(
127 | message,
128 | style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 16),
129 | );
130 | }
131 | }
132 |
133 | class _PictureGrid extends StatelessWidget {
134 | const _PictureGrid();
135 |
136 | @override
137 | Widget build(BuildContext context) {
138 | return GridView.builder(
139 | shrinkWrap: true,
140 | physics: const NeverScrollableScrollPhysics(),
141 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
142 | crossAxisCount: 2,
143 | crossAxisSpacing: 4,
144 | mainAxisSpacing: 4,
145 | ),
146 | itemCount: 4,
147 | itemBuilder: (context, index) {
148 | return Image.asset(
149 | 'reply/attachments/paris_${index + 1}.jpg',
150 | gaplessPlayback: true,
151 | package: 'flutter_gallery_assets',
152 | fit: BoxFit.fill,
153 | );
154 | },
155 | );
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/lib/mail_view_router.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:reply/custom_transition_page.dart';
5 |
6 | import 'home.dart';
7 | import 'inbox.dart';
8 | import 'model/email_store.dart';
9 |
10 | class MailViewRouterDelegate extends RouterDelegate
11 | with ChangeNotifier, PopNavigatorRouterDelegateMixin {
12 | MailViewRouterDelegate({required this.drawerController});
13 |
14 | final AnimationController drawerController;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | bool handlePopPage(Route route, dynamic result) {
19 | return false;
20 | }
21 |
22 | return Selector(
23 | selector: (context, emailStore) => emailStore.currentlySelectedInbox,
24 | builder: (context, currentlySelectedInbox, child) {
25 | return Navigator(
26 | key: navigatorKey,
27 | onPopPage: handlePopPage,
28 | pages: [
29 | // TODO: Add Fade through transition between mailbox pages (Motion)
30 | CustomTransitionPage(
31 | transitionKey: ValueKey(currentlySelectedInbox),
32 | screen: InboxPage(
33 | destination: currentlySelectedInbox,
34 | ),
35 | )
36 | ],
37 | );
38 | },
39 | );
40 | }
41 |
42 | @override
43 | GlobalKey get navigatorKey => mobileMailNavKey;
44 |
45 | @override
46 | Future popRoute() {
47 | var emailStore =
48 | Provider.of(navigatorKey.currentContext!, listen: false);
49 | bool onCompose = emailStore.onCompose;
50 |
51 | bool onMailView = emailStore.onMailView;
52 |
53 | // Handles the back button press when we are on the HomePage. When the
54 | // drawer is visible reverse the drawer and do nothing else. If the drawer
55 | // is not visible then we check if we are on the main mailbox. If we are on
56 | // main mailbox then our app will close, if not then it will set the
57 | // mailbox to the main mailbox.
58 | if (!(onMailView || onCompose)) {
59 | if (emailStore.bottomDrawerVisible) {
60 | drawerController.reverse();
61 | return SynchronousFuture(true);
62 | }
63 |
64 | if (emailStore.currentlySelectedInbox != 'Inbox') {
65 | emailStore.currentlySelectedInbox = 'Inbox';
66 | return SynchronousFuture(true);
67 | }
68 | return SynchronousFuture(false);
69 | }
70 |
71 | // Handles the back button when on the [ComposePage].
72 | if (onCompose) {
73 | // TODO: Add Container Transform from FAB to compose email page (Motion)
74 | emailStore.onCompose = false;
75 | return SynchronousFuture(false);
76 | }
77 |
78 | // Handles the back button when the bottom drawer is visible on the
79 | // MailView. Dismisses the drawer on back button press.
80 | if (emailStore.bottomDrawerVisible && onMailView) {
81 | drawerController.reverse();
82 | return SynchronousFuture(true);
83 | }
84 |
85 | // Handles the back button press when on the MailView. If there is a route
86 | // to pop then pop it, and reset the currentlySelectedEmailId to -1
87 | // to notify listeners that we are no longer on the MailView.
88 | if (navigatorKey.currentState!.canPop()) {
89 | navigatorKey.currentState!.pop();
90 | Provider.of(navigatorKey.currentContext!, listen: false)
91 | .currentlySelectedEmailId = -1;
92 | return SynchronousFuture(true);
93 | }
94 |
95 | return SynchronousFuture(false);
96 | }
97 |
98 | @override
99 | Future setNewRoutePath(void configuration) {
100 | // This function will never be called.
101 | throw UnimplementedError();
102 | }
103 | }
104 |
105 | // TODO: Add Fade through transition between mailbox pages (Motion)
106 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'app.dart';
4 |
5 | void main() => runApp(const ReplyApp());
6 |
--------------------------------------------------------------------------------
/lib/model/email_model.dart:
--------------------------------------------------------------------------------
1 | class Email {
2 | const Email({
3 | required this.sender,
4 | required this.time,
5 | required this.subject,
6 | required this.message,
7 | required this.avatar,
8 | required this.recipients,
9 | required this.containsPictures,
10 | });
11 |
12 | final String sender;
13 | final String time;
14 | final String subject;
15 | final String message;
16 | final String avatar;
17 | final String recipients;
18 | final bool containsPictures;
19 | }
20 |
--------------------------------------------------------------------------------
/lib/model/email_store.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/scheduler.dart';
3 | import 'package:reply/settings_bottom_sheet.dart';
4 |
5 | import 'email_model.dart';
6 |
7 | const _avatarsLocation = 'reply/avatars';
8 |
9 | class EmailStore with ChangeNotifier {
10 | final _categories = >{
11 | 'Inbox': _mainInbox,
12 | 'Starred': _starredInbox,
13 | 'Sent': _outbox,
14 | 'Trash': _trash,
15 | 'Spam': _spam,
16 | 'Drafts': _drafts,
17 | };
18 |
19 | static final _mainInbox = {
20 | const Email(
21 | sender: 'Google Express',
22 | time: '15 minutes ago',
23 | subject: 'Package shipped!',
24 | message: 'Cucumber Mask Facial has shipped.\n\n'
25 | 'Keep an eye out for a package to arrive between this Thursday and next Tuesday. If for any reason you don\'t receive your package before the end of next week, please reach out to us for details on your shipment.\n\n'
26 | 'As always, thank you for shopping with us and we hope you love our specially formulated Cucumber Mask!',
27 | avatar: '$_avatarsLocation/avatar_express.png',
28 | recipients: 'Jeff',
29 | containsPictures: false,
30 | ),
31 | const Email(
32 | sender: 'Ali Connors',
33 | time: '4 hrs ago',
34 | subject: 'Brunch this weekend?',
35 | message:
36 | 'I\'ll be in your neighborhood doing errands and was hoping to catch you for a coffee this Saturday. If you don\'t have anything scheduled, it would be great to see you! It feels like its been forever.\n\n'
37 | 'If we do get a chance to get together, remind me to tell you about Kim. She stopped over at the house to say hey to the kids and told me all about her trip to Mexico.\n\n'
38 | 'Talk to you soon,\n\n'
39 | 'Ali',
40 | avatar: '$_avatarsLocation/avatar_5.jpg',
41 | recipients: 'Jeff',
42 | containsPictures: false,
43 | ),
44 | const Email(
45 | sender: 'Allison Trabucco',
46 | time: '5 hrs ago',
47 | subject: 'Bonjour from Paris',
48 | message: 'Here are some great shots from my trip...',
49 | avatar: '$_avatarsLocation/avatar_3.jpg',
50 | recipients: 'Jeff',
51 | containsPictures: true,
52 | ),
53 | const Email(
54 | sender: 'Trevor Hansen',
55 | time: '9 hrs ago',
56 | subject: 'Brazil trip',
57 | message:
58 | 'Thought we might be able to go over some details about our upcoming vacation.\n\n'
59 | 'I\'ve been doing a bit of research and have come across a few paces in Northern Brazil that I think we should check out. '
60 | 'One, the north has some of the most predictable wind on the planet. '
61 | 'I\'d love to get out on the ocean and kitesurf for a couple of days if we\'re going to be anywhere near or around Taiba. '
62 | 'I hear it\'s beautiful there and if you\'re up for it, I\'d love to go. Other than that, I haven\'t spent too much time looking into places along our road trip route. '
63 | 'I\'m assuming we can find places to stay and things to do as we drive and find places we think look interesting. But... I know you\'re more of a planner, so if you have ideas or places in mind, lets jot some ideas down!\n\n'
64 | 'Maybe we can jump on the phone later today if you have a second.',
65 | avatar: '$_avatarsLocation/avatar_8.jpg',
66 | recipients: 'Allison, Kim, Jeff',
67 | containsPictures: false,
68 | ),
69 | const Email(
70 | sender: 'Frank Hawkins',
71 | time: '10 hrs ago',
72 | subject: 'Update to Your Itinerary',
73 | message: '',
74 | avatar: '$_avatarsLocation/avatar_4.jpg',
75 | recipients: 'Jeff',
76 | containsPictures: false,
77 | ),
78 | const Email(
79 | sender: 'Google Express',
80 | time: '12 hrs ago',
81 | subject: 'Delivered',
82 | message: 'Your shoes should be waiting for you at home!',
83 | avatar: '$_avatarsLocation/avatar_express.png',
84 | recipients: 'Jeff',
85 | containsPictures: false,
86 | ),
87 | };
88 |
89 | static final _starredInbox = {};
90 |
91 | static final _outbox = {
92 | const Email(
93 | sender: 'Kim Alen',
94 | time: '4 hrs ago',
95 | subject: 'High school reunion?',
96 | message:
97 | 'Hi friends,\n\nI was at the grocery store on Sunday night.. when I ran into Genie Williams! I almost didn\'t recognize her afer 20 years!\n\n'
98 | 'Anyway, it turns out she is on the organizing committee for the high school reunion this fall. I don\'t know if you were planning on going or not, but she could definitely use our help in trying to track down lots of missing alums. '
99 | 'If you can make it, we\'re doing a little phone-tree party at her place next Saturday, hoping that if we can find one person, thee more will...',
100 | avatar: '$_avatarsLocation/avatar_7.jpg',
101 | recipients: 'Jeff',
102 | containsPictures: false,
103 | ),
104 | const Email(
105 | sender: 'Sandra Adams',
106 | time: '7 hrs ago',
107 | subject: 'Recipe to try',
108 | message:
109 | 'Raspberry Pie: We should make this pie recipe tonight! The filling is '
110 | 'very quick to put together.',
111 | avatar: '$_avatarsLocation/avatar_2.jpg',
112 | recipients: 'Jeff',
113 | containsPictures: false,
114 | ),
115 | };
116 |
117 | static final _trash = {
118 | const Email(
119 | sender: 'Frank Hawkins',
120 | time: '4 hrs ago',
121 | subject: 'Your update on the Google Play Store is live!',
122 | message:
123 | 'Your update is now live on the Play Store and available for your alpha users to start testing.\n\n'
124 | 'Your alpha testers will be automatically notified. If you\'d rather send them a link directly, go to your Google Play Console and follow the instructions for obtaining an open alpha testing link.',
125 | avatar: '$_avatarsLocation/avatar_4.jpg',
126 | recipients: 'Jeff',
127 | containsPictures: false,
128 | ),
129 | const Email(
130 | sender: 'Allison Trabucco',
131 | time: '6 hrs ago',
132 | subject: 'Try a free TrailGo account',
133 | message:
134 | 'Looking for the best hiking trails in your area? TrailGo gets you on the path to the outdoors faster than you can pack a sandwich.\n\n'
135 | 'Whether you\'re an experienced hiker or just looking to get outside for the afternoon, there\'s a segment that suits you.',
136 | avatar: '$_avatarsLocation/avatar_3.jpg',
137 | recipients: 'Jeff',
138 | containsPictures: false,
139 | ),
140 | };
141 |
142 | static final _spam = {
143 | const Email(
144 | sender: 'Allison Trabucco',
145 | time: '4 hrs ago',
146 | subject: 'Free money',
147 | message:
148 | 'You\'ve been selected as a winner in our latest raffle! To claim your prize, click on the link.',
149 | avatar: '$_avatarsLocation/avatar_3.jpg',
150 | recipients: 'Jeff',
151 | containsPictures: false,
152 | ),
153 | };
154 |
155 | static final _drafts = {
156 | const Email(
157 | sender: 'Sandra Adams',
158 | time: '2 hrs ago',
159 | subject: '(No subject)',
160 | message: 'Hey,\n\n'
161 | 'Wanted to email and see what you thought of',
162 | avatar: '$_avatarsLocation/avatar_2.jpg',
163 | recipients: 'Jeff',
164 | containsPictures: false,
165 | ),
166 | };
167 |
168 | int _currentlySelectedEmailId = -1;
169 | String _currentlySelectedInbox = 'Inbox';
170 | bool _onCompose = false;
171 | bool _bottomDrawerVisible = false;
172 | ThemeMode _currentTheme = ThemeMode.system;
173 | SlowMotionSpeedSetting _currentSlowMotionSpeed =
174 | SlowMotionSpeedSetting.normal;
175 |
176 | Map> get emails =>
177 | Map>.unmodifiable(_categories);
178 |
179 | void deleteEmail(String category, int id) {
180 | final email = _categories[category]!.elementAt(id);
181 |
182 | _categories.forEach(
183 | (key, value) {
184 | if (value.contains(email)) {
185 | value.remove(email);
186 | }
187 | },
188 | );
189 |
190 | notifyListeners();
191 | }
192 |
193 | void starEmail(String category, int id) {
194 | final email = _categories[category]!.elementAt(id);
195 | var alreadyStarred = isEmailStarred(email);
196 |
197 | if (alreadyStarred) {
198 | _categories['Starred']!.remove(email);
199 | } else {
200 | _categories['Starred']!.add(email);
201 | }
202 |
203 | notifyListeners();
204 | }
205 |
206 | bool get bottomDrawerVisible => _bottomDrawerVisible;
207 | int get currentlySelectedEmailId => _currentlySelectedEmailId;
208 | String get currentlySelectedInbox => _currentlySelectedInbox;
209 | bool get onMailView => _currentlySelectedEmailId > -1;
210 | bool get onCompose => _onCompose;
211 | ThemeMode get themeMode => _currentTheme;
212 | SlowMotionSpeedSetting get slowMotionSpeed => _currentSlowMotionSpeed;
213 |
214 | bool isEmailStarred(Email email) {
215 | return _categories['Starred']!.contains(email);
216 | }
217 |
218 | set bottomDrawerVisible(bool value) {
219 | _bottomDrawerVisible = value;
220 | notifyListeners();
221 | }
222 |
223 | set currentlySelectedEmailId(int value) {
224 | _currentlySelectedEmailId = value;
225 | notifyListeners();
226 | }
227 |
228 | set currentlySelectedInbox(String inbox) {
229 | _currentlySelectedInbox = inbox;
230 | notifyListeners();
231 | }
232 |
233 | set themeMode(ThemeMode theme) {
234 | _currentTheme = theme;
235 | notifyListeners();
236 | }
237 |
238 | set slowMotionSpeed(SlowMotionSpeedSetting speed) {
239 | _currentSlowMotionSpeed = speed;
240 | timeDilation = slowMotionSpeed.value;
241 | }
242 |
243 | set onCompose(bool value) {
244 | _onCompose = value;
245 | notifyListeners();
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/lib/model/router_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:reply/router.dart';
3 |
4 | class RouterProvider with ChangeNotifier {
5 | RouterProvider(ReplyHomePath this._routePath);
6 |
7 | ReplyRoutePath _routePath;
8 | ReplyRoutePath get routePath => _routePath;
9 |
10 | set routePath(ReplyRoutePath? route) {
11 | if (route != _routePath) {
12 | _routePath = route!;
13 | notifyListeners();
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/profile_avatar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ProfileAvatar extends StatelessWidget {
4 | const ProfileAvatar({
5 | required this.avatar,
6 | this.radius = 20,
7 | Key? key,
8 | }) : super(key: key);
9 |
10 | final String avatar;
11 | final double radius;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Material(
16 | color: Colors.transparent,
17 | child: CircleAvatar(
18 | radius: radius,
19 | backgroundColor: Theme.of(context).cardColor,
20 | child: ClipOval(
21 | child: Image.asset(
22 | avatar,
23 | package: 'flutter_gallery_assets',
24 | height: 42,
25 | width: 42,
26 | fit: BoxFit.cover,
27 | ),
28 | ),
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:reply/custom_transition_page.dart';
5 | import 'package:reply/home.dart';
6 | import 'package:reply/search_page.dart';
7 |
8 | import 'model/router_provider.dart';
9 |
10 | const String _homePageLocation = '/reply/home';
11 | const String _searchPageLocation = '/reply/search';
12 |
13 | class ReplyRouterDelegate extends RouterDelegate
14 | with ChangeNotifier, PopNavigatorRouterDelegateMixin {
15 | ReplyRouterDelegate({required this.replyState})
16 | : navigatorKey = GlobalObjectKey(replyState) {
17 | replyState.addListener(() {
18 | notifyListeners();
19 | });
20 | }
21 |
22 | @override
23 | final GlobalKey navigatorKey;
24 |
25 | RouterProvider replyState;
26 |
27 | @override
28 | void dispose() {
29 | replyState.removeListener(notifyListeners);
30 | super.dispose();
31 | }
32 |
33 | @override
34 | ReplyRoutePath get currentConfiguration => replyState.routePath;
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return MultiProvider(
39 | providers: [
40 | ChangeNotifierProvider.value(value: replyState),
41 | ],
42 | child: Selector(
43 | selector: (context, routerProvider) => routerProvider.routePath,
44 | builder: (context, routePath, child) {
45 | return Navigator(
46 | key: navigatorKey,
47 | onPopPage: _handlePopPage,
48 | pages: [
49 | // TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
50 | const CustomTransitionPage(
51 | transitionKey: ValueKey('Home'),
52 | screen: HomePage(),
53 | ),
54 | if (routePath is ReplySearchPath)
55 | const CustomTransitionPage(
56 | transitionKey: ValueKey('Search'),
57 | screen: SearchPage(),
58 | ),
59 | ],
60 | );
61 | },
62 | ),
63 | );
64 | }
65 |
66 | bool _handlePopPage(Route route, dynamic result) {
67 | // _handlePopPage should not be called on the home page because the
68 | // PopNavigatorRouterDelegateMixin will bubble up the pop to the
69 | // SystemNavigator if there is only one route in the navigator.
70 | assert(route.willHandlePopInternally ||
71 | replyState.routePath is ReplySearchPath);
72 |
73 | final bool didPop = route.didPop(result);
74 | if (didPop) replyState.routePath = const ReplyHomePath();
75 | return didPop;
76 | }
77 |
78 | @override
79 | Future setNewRoutePath(ReplyRoutePath configuration) {
80 | replyState.routePath = configuration;
81 | return SynchronousFuture(null);
82 | }
83 | }
84 |
85 | @immutable
86 | abstract class ReplyRoutePath {
87 | const ReplyRoutePath();
88 | }
89 |
90 | class ReplyHomePath extends ReplyRoutePath {
91 | const ReplyHomePath();
92 | }
93 |
94 | class ReplySearchPath extends ReplyRoutePath {
95 | const ReplySearchPath();
96 | }
97 |
98 | // TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
99 |
100 | class ReplyRouteInformationParser
101 | extends RouteInformationParser {
102 | @override
103 | Future parseRouteInformation(
104 | RouteInformation routeInformation) async {
105 | final url = Uri.parse(routeInformation.location!);
106 |
107 | if (url.path == _searchPageLocation) {
108 | return SynchronousFuture(const ReplySearchPath());
109 | }
110 |
111 | return SynchronousFuture(const ReplyHomePath());
112 | }
113 |
114 | @override
115 | RouteInformation? restoreRouteInformation(ReplyRoutePath configuration) {
116 | if (configuration is ReplyHomePath) {
117 | return const RouteInformation(location: _homePageLocation);
118 | }
119 | if (configuration is ReplySearchPath) {
120 | return const RouteInformation(location: _searchPageLocation);
121 | }
122 | return null;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/search_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:reply/model/router_provider.dart';
4 | import 'package:reply/router.dart';
5 |
6 | class SearchPage extends StatelessWidget {
7 | const SearchPage({Key? key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | body: SafeArea(
13 | child: Material(
14 | color: Theme.of(context).colorScheme.surface,
15 | child: Column(
16 | children: [
17 | Padding(
18 | padding: const EdgeInsets.all(8),
19 | child: Row(
20 | mainAxisSize: MainAxisSize.max,
21 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
22 | children: [
23 | BackButton(
24 | onPressed: () {
25 | Provider.of(
26 | context,
27 | listen: false,
28 | ).routePath = const ReplyHomePath();
29 | },
30 | ),
31 | const Expanded(
32 | child: TextField(
33 | decoration: InputDecoration.collapsed(
34 | hintText: 'Search email',
35 | ),
36 | ),
37 | ),
38 | IconButton(
39 | icon: const Icon(Icons.mic),
40 | onPressed: () {},
41 | )
42 | ],
43 | ),
44 | ),
45 | const Divider(thickness: 1),
46 | Expanded(
47 | child: SingleChildScrollView(
48 | child: Column(
49 | crossAxisAlignment: CrossAxisAlignment.start,
50 | children: const [
51 | _SectionHeader(title: 'YESTERDAY'),
52 | _SearchHistoryTile(
53 | search: '481 Van Brunt Street',
54 | address: 'Brooklyn, NY',
55 | ),
56 | _SearchHistoryTile(
57 | icon: Icons.home,
58 | search: 'Home',
59 | address: '199 Pacific Street, Brooklyn, NY',
60 | ),
61 | _SectionHeader(title: 'THIS WEEK'),
62 | _SearchHistoryTile(
63 | search: 'BEP GA',
64 | address: 'Forsyth Street, New York, NY',
65 | ),
66 | _SearchHistoryTile(
67 | search: 'Sushi Nakazawa',
68 | address: 'Commerce Street, New York, NY',
69 | ),
70 | _SearchHistoryTile(
71 | search: 'IFC Center',
72 | address: '6th Avenue, New York, NY',
73 | ),
74 | ],
75 | ),
76 | ),
77 | ),
78 | ],
79 | ),
80 | ),
81 | ),
82 | );
83 | }
84 | }
85 |
86 | class _SectionHeader extends StatelessWidget {
87 | const _SectionHeader({
88 | required this.title,
89 | });
90 | final String title;
91 |
92 | @override
93 | Widget build(BuildContext context) {
94 | return Padding(
95 | padding: const EdgeInsetsDirectional.only(
96 | start: 16,
97 | top: 16,
98 | bottom: 16,
99 | ),
100 | child: Text(
101 | title,
102 | style: Theme.of(context).textTheme.labelLarge,
103 | ),
104 | );
105 | }
106 | }
107 |
108 | class _SearchHistoryTile extends StatelessWidget {
109 | const _SearchHistoryTile({
110 | this.icon = Icons.access_time,
111 | required this.search,
112 | required this.address,
113 | });
114 |
115 | final IconData icon;
116 | final String search;
117 | final String address;
118 |
119 | @override
120 | Widget build(BuildContext context) {
121 | return ListTile(
122 | leading: Icon(icon),
123 | title: Text(search),
124 | subtitle: Text(address),
125 | onTap: () {},
126 | );
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/lib/settings_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import 'model/email_store.dart';
5 |
6 | enum SlowMotionSpeedSetting { normal, slow, slower, slowest }
7 |
8 | extension AnimationSpeedSettingExtension on SlowMotionSpeedSetting {
9 | double get value {
10 | switch (this) {
11 | case SlowMotionSpeedSetting.normal:
12 | return 1.0;
13 | case SlowMotionSpeedSetting.slow:
14 | return 5.0;
15 | case SlowMotionSpeedSetting.slower:
16 | return 10.0;
17 | case SlowMotionSpeedSetting.slowest:
18 | return 15.0;
19 | }
20 | }
21 | }
22 |
23 | extension ThemeModeExtension on ThemeMode {
24 | String get name {
25 | switch (this) {
26 | case ThemeMode.system:
27 | return 'System';
28 | case ThemeMode.light:
29 | return 'Light';
30 | case ThemeMode.dark:
31 | return 'Dark';
32 | }
33 | }
34 | }
35 |
36 | class SettingsBottomSheet extends StatefulWidget {
37 | const SettingsBottomSheet({Key? key}) : super(key: key);
38 |
39 | @override
40 | SettingsBottomSheetState createState() => SettingsBottomSheetState();
41 | }
42 |
43 | class SettingsBottomSheetState extends State {
44 | late SlowMotionSpeedSetting _slowMotionSpeedSetting;
45 | late ThemeMode _themeMode;
46 |
47 | @override
48 | void initState() {
49 | super.initState();
50 | _themeMode = Provider.of(context, listen: false).themeMode;
51 | _slowMotionSpeedSetting =
52 | Provider.of(context, listen: false).slowMotionSpeed;
53 | }
54 |
55 | @override
56 | Widget build(BuildContext context) {
57 | var radius = const Radius.circular(12);
58 | final modalBorder = BorderRadius.only(
59 | topRight: radius,
60 | topLeft: radius,
61 | );
62 |
63 | return StatefulBuilder(builder: (context, state) {
64 | void setTheme(ThemeMode? theme) {
65 | state(() {
66 | _themeMode = theme!;
67 | });
68 | Provider.of(context, listen: false).themeMode = theme!;
69 | }
70 |
71 | void setSlowMotionSpeed(SlowMotionSpeedSetting? slowMotionSpeed) {
72 | state(() {
73 | _slowMotionSpeedSetting = slowMotionSpeed!;
74 | });
75 | Provider.of(context, listen: false).slowMotionSpeed =
76 | slowMotionSpeed!;
77 | }
78 |
79 | return Container(
80 | decoration: BoxDecoration(
81 | borderRadius: modalBorder,
82 | color: Theme.of(context).colorScheme.surface,
83 | ),
84 | child: SingleChildScrollView(
85 | child: Column(
86 | children: [
87 | ExpansionTile(
88 | title: const Text('Theme'),
89 | children: [
90 | for (var themeMode in ThemeMode.values)
91 | RadioListTile(
92 | title: Text(themeMode.name),
93 | value: themeMode,
94 | groupValue: _themeMode,
95 | onChanged: setTheme,
96 | ),
97 | ],
98 | ),
99 | ExpansionTile(
100 | title: const Text('Slow Motion'),
101 | children: [
102 | for (var animationSpeed in SlowMotionSpeedSetting.values)
103 | RadioListTile(
104 | title: Text('${animationSpeed.value.toInt()}x'),
105 | value: animationSpeed,
106 | groupValue: _slowMotionSpeedSetting,
107 | onChanged: setSlowMotionSpeed,
108 | ),
109 | ],
110 | ),
111 | ],
112 | ),
113 | ),
114 | );
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/waterfall_notched_rectangle.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | /// A rectangle with a smooth circular notch.
6 | ///
7 | /// See also:
8 | ///
9 | /// * [CircleBorder], a [ShapeBorder] that describes a circle.
10 | class WaterfallNotchedRectangle extends NotchedShape {
11 | /// Creates a [WaterfallNotchedRectangle].
12 | ///
13 | /// The same object can be used to create multiple shapes.
14 | const WaterfallNotchedRectangle();
15 |
16 | /// Creates a [Path] that describes a rectangle with a smooth circular notch.
17 | ///
18 | /// `host` is the bounding box for the returned shape. Conceptually this is
19 | /// the rectangle to which the notch will be applied.
20 | ///
21 | /// `guest` is the bounding box of a circle that the notch accommodates. All
22 | /// points in the circle bounded by `guest` will be outside of the returned
23 | /// path.
24 | ///
25 | /// The notch is curve that smoothly connects the host's top edge and
26 | /// the guest circle.
27 | @override
28 | Path getOuterPath(Rect host, Rect? guest) {
29 | if (guest == null || !host.overlaps(guest)) return Path()..addRect(host);
30 |
31 | // The guest's shape is a circle bounded by the guest rectangle.
32 | // So the guest's radius is half the guest width.
33 | final double notchRadius = guest.width / 2.0;
34 |
35 | // We build a path for the notch from 3 segments:
36 | // Segment A - a Bezier curve from the host's top edge to segment B.
37 | // Segment B - an arc with radius notchRadius.
38 | // Segment C - a Bezier curve from segment B back to the host's top edge.
39 | //
40 | // A detailed explanation and the derivation of the formulas below is
41 | // available at: https://goo.gl/Ufzrqn
42 |
43 | // s1, s2 are the two knobs controlling the behavior of the bezzier curve.
44 | const double s1 = 21.0;
45 | const double s2 = 6.0;
46 |
47 | final double r = notchRadius;
48 | final double a = -1.0 * r - s2;
49 | final double b = host.top - guest.center.dy;
50 |
51 | final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r));
52 | final double p2xA = ((a * r * r) - n2) / (a * a + b * b);
53 | final double p2xB = ((a * r * r) + n2) / (a * a + b * b);
54 | final double p2yA = math.sqrt(r * r - p2xA * p2xA);
55 | final double p2yB = math.sqrt(r * r - p2xB * p2xB);
56 |
57 | final List p = List.filled(6, null, growable: false);
58 |
59 | // p0, p1, and p2 are the control points for segment A.
60 | p[0] = Offset(a - s1, b);
61 | p[1] = Offset(a, b);
62 | final double cmp = b < 0 ? -1.0 : 1.0;
63 | p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB);
64 |
65 | // p3, p4, and p5 are the control points for segment B, which is a mirror
66 | // of segment A around the y axis.
67 | p[3] = Offset(-1.0 * p[2]!.dx, p[2]!.dy);
68 | p[4] = Offset(-1.0 * p[1]!.dx, p[1]!.dy);
69 | p[5] = Offset(-1.0 * p[0]!.dx, p[0]!.dy);
70 |
71 | // translate all points back to the absolute coordinate system.
72 | for (int i = 0; i < p.length; i += 1) {
73 | p[i] = p[i]! + guest.center;
74 | }
75 |
76 | return Path()
77 | ..moveTo(host.left, host.top)
78 | ..lineTo(p[0]!.dx, p[0]!.dy)
79 | ..quadraticBezierTo(p[1]!.dx, p[1]!.dy, p[2]!.dx, p[2]!.dy)
80 | ..arcToPoint(
81 | p[3]!,
82 | radius: Radius.circular(notchRadius),
83 | clockwise: false,
84 | )
85 | ..quadraticBezierTo(p[4]!.dx, p[4]!.dy, p[5]!.dx, p[5]!.dy)
86 | ..lineTo(host.right, host.top)
87 | ..lineTo(host.right, host.bottom)
88 | ..lineTo(host.left, host.bottom)
89 | ..close();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | animations:
5 | dependency: "direct main"
6 | description:
7 | name: animations
8 | sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.0.11"
12 | async:
13 | dependency: transitive
14 | description:
15 | name: async
16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.11.0"
20 | boolean_selector:
21 | dependency: transitive
22 | description:
23 | name: boolean_selector
24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.1.1"
28 | characters:
29 | dependency: transitive
30 | description:
31 | name: characters
32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.3.0"
36 | clock:
37 | dependency: transitive
38 | description:
39 | name: clock
40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.1.1"
44 | collection:
45 | dependency: transitive
46 | description:
47 | name: collection
48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.18.0"
52 | crypto:
53 | dependency: transitive
54 | description:
55 | name: crypto
56 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "3.0.3"
60 | cupertino_icons:
61 | dependency: "direct main"
62 | description:
63 | name: cupertino_icons
64 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "1.0.8"
68 | fake_async:
69 | dependency: transitive
70 | description:
71 | name: fake_async
72 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "1.3.1"
76 | ffi:
77 | dependency: transitive
78 | description:
79 | name: ffi
80 | sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "2.1.2"
84 | flutter:
85 | dependency: "direct main"
86 | description: flutter
87 | source: sdk
88 | version: "0.0.0"
89 | flutter_gallery_assets:
90 | dependency: "direct main"
91 | description:
92 | name: flutter_gallery_assets
93 | sha256: f8fecfeebcfbe80a2fabc00c0834046890abe681f59d3e2c5b1028faf887d94b
94 | url: "https://pub.dev"
95 | source: hosted
96 | version: "1.0.2"
97 | flutter_lints:
98 | dependency: "direct dev"
99 | description:
100 | name: flutter_lints
101 | sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
102 | url: "https://pub.dev"
103 | source: hosted
104 | version: "4.0.0"
105 | flutter_test:
106 | dependency: "direct dev"
107 | description: flutter
108 | source: sdk
109 | version: "0.0.0"
110 | google_fonts:
111 | dependency: "direct main"
112 | description:
113 | name: google_fonts
114 | sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
115 | url: "https://pub.dev"
116 | source: hosted
117 | version: "6.2.1"
118 | http:
119 | dependency: transitive
120 | description:
121 | name: http
122 | sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
123 | url: "https://pub.dev"
124 | source: hosted
125 | version: "1.2.1"
126 | http_parser:
127 | dependency: transitive
128 | description:
129 | name: http_parser
130 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
131 | url: "https://pub.dev"
132 | source: hosted
133 | version: "4.0.2"
134 | leak_tracker:
135 | dependency: transitive
136 | description:
137 | name: leak_tracker
138 | sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
139 | url: "https://pub.dev"
140 | source: hosted
141 | version: "10.0.4"
142 | leak_tracker_flutter_testing:
143 | dependency: transitive
144 | description:
145 | name: leak_tracker_flutter_testing
146 | sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
147 | url: "https://pub.dev"
148 | source: hosted
149 | version: "3.0.3"
150 | leak_tracker_testing:
151 | dependency: transitive
152 | description:
153 | name: leak_tracker_testing
154 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
155 | url: "https://pub.dev"
156 | source: hosted
157 | version: "3.0.1"
158 | lints:
159 | dependency: transitive
160 | description:
161 | name: lints
162 | sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
163 | url: "https://pub.dev"
164 | source: hosted
165 | version: "4.0.0"
166 | matcher:
167 | dependency: transitive
168 | description:
169 | name: matcher
170 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
171 | url: "https://pub.dev"
172 | source: hosted
173 | version: "0.12.16+1"
174 | material_color_utilities:
175 | dependency: transitive
176 | description:
177 | name: material_color_utilities
178 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
179 | url: "https://pub.dev"
180 | source: hosted
181 | version: "0.8.0"
182 | meta:
183 | dependency: transitive
184 | description:
185 | name: meta
186 | sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
187 | url: "https://pub.dev"
188 | source: hosted
189 | version: "1.12.0"
190 | nested:
191 | dependency: transitive
192 | description:
193 | name: nested
194 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
195 | url: "https://pub.dev"
196 | source: hosted
197 | version: "1.0.0"
198 | path:
199 | dependency: transitive
200 | description:
201 | name: path
202 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
203 | url: "https://pub.dev"
204 | source: hosted
205 | version: "1.9.0"
206 | path_provider:
207 | dependency: transitive
208 | description:
209 | name: path_provider
210 | sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
211 | url: "https://pub.dev"
212 | source: hosted
213 | version: "2.1.3"
214 | path_provider_android:
215 | dependency: transitive
216 | description:
217 | name: path_provider_android
218 | sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
219 | url: "https://pub.dev"
220 | source: hosted
221 | version: "2.2.6"
222 | path_provider_foundation:
223 | dependency: transitive
224 | description:
225 | name: path_provider_foundation
226 | sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
227 | url: "https://pub.dev"
228 | source: hosted
229 | version: "2.4.0"
230 | path_provider_linux:
231 | dependency: transitive
232 | description:
233 | name: path_provider_linux
234 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
235 | url: "https://pub.dev"
236 | source: hosted
237 | version: "2.2.1"
238 | path_provider_platform_interface:
239 | dependency: transitive
240 | description:
241 | name: path_provider_platform_interface
242 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
243 | url: "https://pub.dev"
244 | source: hosted
245 | version: "2.1.2"
246 | path_provider_windows:
247 | dependency: transitive
248 | description:
249 | name: path_provider_windows
250 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
251 | url: "https://pub.dev"
252 | source: hosted
253 | version: "2.2.1"
254 | platform:
255 | dependency: transitive
256 | description:
257 | name: platform
258 | sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
259 | url: "https://pub.dev"
260 | source: hosted
261 | version: "3.1.5"
262 | plugin_platform_interface:
263 | dependency: transitive
264 | description:
265 | name: plugin_platform_interface
266 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
267 | url: "https://pub.dev"
268 | source: hosted
269 | version: "2.1.8"
270 | provider:
271 | dependency: "direct main"
272 | description:
273 | name: provider
274 | sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
275 | url: "https://pub.dev"
276 | source: hosted
277 | version: "6.1.2"
278 | sky_engine:
279 | dependency: transitive
280 | description: flutter
281 | source: sdk
282 | version: "0.0.99"
283 | source_span:
284 | dependency: transitive
285 | description:
286 | name: source_span
287 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
288 | url: "https://pub.dev"
289 | source: hosted
290 | version: "1.10.0"
291 | stack_trace:
292 | dependency: transitive
293 | description:
294 | name: stack_trace
295 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
296 | url: "https://pub.dev"
297 | source: hosted
298 | version: "1.11.1"
299 | stream_channel:
300 | dependency: transitive
301 | description:
302 | name: stream_channel
303 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
304 | url: "https://pub.dev"
305 | source: hosted
306 | version: "2.1.2"
307 | string_scanner:
308 | dependency: transitive
309 | description:
310 | name: string_scanner
311 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
312 | url: "https://pub.dev"
313 | source: hosted
314 | version: "1.2.0"
315 | term_glyph:
316 | dependency: transitive
317 | description:
318 | name: term_glyph
319 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
320 | url: "https://pub.dev"
321 | source: hosted
322 | version: "1.2.1"
323 | test_api:
324 | dependency: transitive
325 | description:
326 | name: test_api
327 | sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
328 | url: "https://pub.dev"
329 | source: hosted
330 | version: "0.7.0"
331 | typed_data:
332 | dependency: transitive
333 | description:
334 | name: typed_data
335 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
336 | url: "https://pub.dev"
337 | source: hosted
338 | version: "1.3.2"
339 | vector_math:
340 | dependency: transitive
341 | description:
342 | name: vector_math
343 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
344 | url: "https://pub.dev"
345 | source: hosted
346 | version: "2.1.4"
347 | vm_service:
348 | dependency: transitive
349 | description:
350 | name: vm_service
351 | sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
352 | url: "https://pub.dev"
353 | source: hosted
354 | version: "14.2.1"
355 | web:
356 | dependency: transitive
357 | description:
358 | name: web
359 | sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
360 | url: "https://pub.dev"
361 | source: hosted
362 | version: "0.5.1"
363 | win32:
364 | dependency: transitive
365 | description:
366 | name: win32
367 | sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
368 | url: "https://pub.dev"
369 | source: hosted
370 | version: "5.5.1"
371 | xdg_directories:
372 | dependency: transitive
373 | description:
374 | name: xdg_directories
375 | sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
376 | url: "https://pub.dev"
377 | source: hosted
378 | version: "1.0.4"
379 | sdks:
380 | dart: ">=3.4.0 <4.0.0"
381 | flutter: ">=3.22.0"
382 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: reply
2 | description: The reply material study built in flutter.
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 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: '>=3.4.0-0 <4.0.0'
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 | # The following adds the Cupertino Icons font to your application.
28 | # Use with the CupertinoIcons class for iOS style icons.
29 | cupertino_icons: ^1.0.3
30 | provider: ^6.0.5
31 | animations: ^2.0.0
32 | google_fonts: ^6.2.1
33 | flutter_gallery_assets: ^1.0.2
34 |
35 | dev_dependencies:
36 | flutter_test:
37 | sdk: flutter
38 | flutter_lints: ^4.0.0
39 |
40 | # For information on the generic Dart part of this file, see the
41 | # following page: https://dart.dev/tools/pub/pubspec
42 |
43 | # The following section is specific to Flutter.
44 | flutter:
45 |
46 | # The following line ensures that the Material Icons font is
47 | # included with your application, so that you can use the icons in
48 | # the material Icons class.
49 | uses-material-design: true
50 |
51 | # To add assets to your application, add an assets section, like this:
52 | assets:
53 | - packages/flutter_gallery_assets/fonts/google_fonts/WorkSans-Regular.ttf
54 | - packages/flutter_gallery_assets/fonts/google_fonts/WorkSans-Medium.ttf
55 | - packages/flutter_gallery_assets/fonts/google_fonts/WorkSans-Bold.ttf
56 | - packages/flutter_gallery_assets/fonts/google_fonts/WorkSans-Thin.ttf
57 | - packages/flutter_gallery_assets/fonts/google_fonts/WorkSans-SemiBold.ttf
58 | - packages/flutter_gallery_assets/reply/attachments/paris_1.jpg
59 | - packages/flutter_gallery_assets/reply/attachments/paris_2.jpg
60 | - packages/flutter_gallery_assets/reply/attachments/paris_3.jpg
61 | - packages/flutter_gallery_assets/reply/attachments/paris_4.jpg
62 | - packages/flutter_gallery_assets/reply/avatars/avatar_0.jpg
63 | - packages/flutter_gallery_assets/reply/avatars/avatar_1.jpg
64 | - packages/flutter_gallery_assets/reply/avatars/avatar_2.jpg
65 | - packages/flutter_gallery_assets/reply/avatars/avatar_3.jpg
66 | - packages/flutter_gallery_assets/reply/avatars/avatar_4.jpg
67 | - packages/flutter_gallery_assets/reply/avatars/avatar_5.jpg
68 | - packages/flutter_gallery_assets/reply/avatars/avatar_6.jpg
69 | - packages/flutter_gallery_assets/reply/avatars/avatar_7.jpg
70 | - packages/flutter_gallery_assets/reply/avatars/avatar_8.jpg
71 | - packages/flutter_gallery_assets/reply/avatars/avatar_9.jpg
72 | - packages/flutter_gallery_assets/reply/avatars/avatar_10.jpg
73 | - packages/flutter_gallery_assets/reply/avatars/avatar_express.png
74 | - packages/flutter_gallery_assets/reply/icons/twotone_add_circle_outline.png
75 | - packages/flutter_gallery_assets/reply/icons/twotone_delete.png
76 | - packages/flutter_gallery_assets/reply/icons/twotone_drafts.png
77 | - packages/flutter_gallery_assets/reply/icons/twotone_error.png
78 | - packages/flutter_gallery_assets/reply/icons/twotone_folder.png
79 | - packages/flutter_gallery_assets/reply/icons/twotone_forward.png
80 | - packages/flutter_gallery_assets/reply/icons/twotone_inbox.png
81 | - packages/flutter_gallery_assets/reply/icons/twotone_send.png
82 | - packages/flutter_gallery_assets/reply/icons/twotone_star_on_background.png
83 | - packages/flutter_gallery_assets/reply/icons/twotone_star.png
84 | - packages/flutter_gallery_assets/reply/icons/twotone_stars.png
85 | - packages/flutter_gallery_assets/reply/reply_logo.png
86 |
87 | # An image asset can refer to one or more resolution-specific "variants", see
88 | # https://flutter.dev/assets-and-images/#resolution-aware.
89 |
90 | # For details regarding adding assets from package dependencies, see
91 | # https://flutter.dev/assets-and-images/#from-packages
92 |
93 | # To add custom fonts to your application, add a fonts section here,
94 | # in this "flutter" section. Each entry in this list should have a
95 | # "family" key with the font family name, and a "fonts" key with a
96 | # list giving the asset and other descriptors for the font. For
97 | # example:
98 | # fonts:
99 | # - family: Schyler
100 | # fonts:
101 | # - asset: fonts/Schyler-Regular.ttf
102 | # - asset: fonts/Schyler-Italic.ttf
103 | # style: italic
104 | # - family: Trajan Pro
105 | # fonts:
106 | # - asset: fonts/TrajanPro.ttf
107 | # - asset: fonts/TrajanPro_Bold.ttf
108 | # weight: 700
109 | #
110 | # For details regarding fonts from package dependencies,
111 | # see https://flutter.dev/custom-fonts/#from-packages
112 |
--------------------------------------------------------------------------------
/screenshots/reply-transitions-android.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/screenshots/reply-transitions-android.gif
--------------------------------------------------------------------------------
/screenshots/reply-transitions-iOS.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/screenshots/reply-transitions-iOS.gif
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 | import 'package:reply/app.dart';
11 |
12 | void main() {
13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(const ReplyApp());
16 |
17 | // Verify that our counter starts at 0.
18 | expect(find.text('0'), findsOneWidget);
19 | expect(find.text('1'), findsNothing);
20 |
21 | // Tap the '+' icon and trigger a frame.
22 | await tester.tap(find.byIcon(Icons.add));
23 | await tester.pump();
24 |
25 | // Verify that our counter has incremented.
26 | expect(find.text('0'), findsNothing);
27 | expect(find.text('1'), findsOneWidget);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/material-components/material-components-flutter-motion-codelab/1508095e80271d53cc1a6716bc6e3d5b1ed7cf8c/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | reply
18 |
19 |
20 |
21 |
24 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reply",
3 | "short_name": "reply",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "The reply material study built in flutter.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------