├── analysis_options.yaml ├── example ├── flutter │ ├── ios │ │ ├── Runner │ │ │ ├── Runner-Bridging-Header.h │ │ │ ├── Assets.xcassets │ │ │ │ ├── LaunchImage.imageset │ │ │ │ │ ├── LaunchImage.png │ │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ │ ├── README.md │ │ │ │ │ └── Contents.json │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ │ └── Contents.json │ │ │ ├── AppDelegate.swift │ │ │ ├── Base.lproj │ │ │ │ ├── Main.storyboard │ │ │ │ └── LaunchScreen.storyboard │ │ │ └── Info.plist │ │ ├── Flutter │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── AppFrameworkInfo.plist │ │ ├── Runner.xcodeproj │ │ │ ├── project.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ │ └── Runner.xcscheme │ │ │ └── project.pbxproj │ │ ├── Runner.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── .gitignore │ │ └── Podfile │ ├── README.md │ ├── android │ │ ├── gradle.properties │ │ ├── .gitignore │ │ ├── app │ │ │ ├── src │ │ │ │ ├── main │ │ │ │ │ ├── res │ │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── values │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── drawable │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── kotlin │ │ │ │ │ │ └── com │ │ │ │ │ │ │ └── example │ │ │ │ │ │ │ └── mailto_example │ │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── debug │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── profile │ │ │ │ │ └── AndroidManifest.xml │ │ │ └── build.gradle │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ ├── settings.gradle │ │ └── build.gradle │ ├── .metadata │ ├── pubspec.yaml │ ├── .gitignore │ └── lib │ │ └── main.dart ├── README.md └── http_server │ └── mailto_example.dart ├── assets ├── dart-server.png └── macos-client.png ├── coverage.sh ├── .travis.yml ├── CONTRIBUTE.md ├── pubspec.yaml ├── .gitignore ├── derry.yaml ├── CHANGELOG.md ├── LICENSE ├── lib └── mailto.dart ├── README.md └── test └── mailto_test.dart /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /assets/dart-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/assets/dart-server.png -------------------------------------------------------------------------------- /assets/macos-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/assets/macos-client.png -------------------------------------------------------------------------------- /example/flutter/README.md: -------------------------------------------------------------------------------- 1 | # mailto_example 2 | 3 | This is a Flutter example app of the [`mailto` package](https://pub.dev/packages/mailto). 4 | -------------------------------------------------------------------------------- /example/flutter/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/flutter/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/flutter/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # `mailto` examples 2 | 3 | You can read the examples' source code on [GitHub](https://github.com/smaho-engineering/mailto/tree/master/example). 4 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pub global activate test_coverage 4 | 5 | pub global run test_coverage 6 | 7 | bash <(curl -s https://codecov.io/bash) 8 | 9 | -------------------------------------------------------------------------------- /example/flutter/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | 3 | dart_task: 4 | - test 5 | - dartfmt 6 | - dartanalyzer: --fatal-warnings lib test 7 | 8 | jobs: 9 | include: 10 | - stage: coverage 11 | script: bash coverage.sh 12 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smaho-engineering/mailto/HEAD/example/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | ## Development 2 | 3 | * **`dartfmt -w .`** - Format code with [`dartfmt`](https://dart.dev/tools/dartfmt) 4 | * **`pub run test`** - Run all tests using the [`test`](https://pub.dev/packages/test) package 5 | * **`dartanalyzer lib test`** - Run the analyzer on the `lib` and `test` folders 6 | * **`bash coverage.sh`** - Get coverage info 7 | -------------------------------------------------------------------------------- /example/flutter/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 18cd7a3601bcffb36fdf2f679f763b5e827c2e8e 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/flutter/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: mailto 2 | version: 2.0.0 3 | description: Simple Dart package for creating mailto links in your Flutter apps 4 | homepage: https://github.com/smaho-engineering/mailto 5 | repository: https://github.com/smaho-engineering/mailto 6 | issue_tracker: https://github.com/smaho-engineering/mailto/issues 7 | environment: 8 | sdk: '>=2.12.0 <3.0.0' 9 | dev_dependencies: 10 | pedantic: ^1.10.0-nullsafety.3 11 | test: ^1.16.0-nullsafety.13 12 | scripts: derry.yaml 13 | -------------------------------------------------------------------------------- /example/flutter/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 | -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/kotlin/com/example/mailto_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.mailto_example 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/flutter/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 | -------------------------------------------------------------------------------- /example/flutter/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/flutter/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: mailto_example 2 | description: Demonstrates how to use the mailto package. Fancy iOS app. 3 | version: 1.0.0+1 4 | publish_to: none 5 | environment: 6 | sdk: ">=2.12.0 <3.0.0" 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | mailto: 11 | path: ../../ 12 | # Most likely, you will want to use the url_launcher in your Flutter app 13 | # if you are using the mailto package. 14 | url_launcher: ^6.0.3 15 | # For a better demo app, not necessary at all 16 | cupertino_icons: ^0.1.2 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | -------------------------------------------------------------------------------- /example/flutter/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/flutter/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/flutter/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/flutter/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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/dart 2 | # Edit at https://www.gitignore.io/?templates=dart 3 | 4 | ### Dart ### 5 | # See https://www.dartlang.org/guides/libraries/private-files 6 | 7 | # Coverage 8 | coverage/ 9 | coverage_badge.svg 10 | test/.test_coverage.dart 11 | 12 | 13 | # Files and directories created by pub 14 | .dart_tool/ 15 | .packages 16 | build/ 17 | 18 | # If you're building an application, you may want to check-in your pubspec.lock 19 | pubspec.lock 20 | 21 | # JetBrains 22 | .idea/ 23 | 24 | # Directory created by dartdoc 25 | # If you don't generate documentation locally you can remove this line. 26 | doc/api/ 27 | 28 | # Avoid committing generated Javascript files: 29 | *.dart.js 30 | *.info.json # Produced by the --dump-info flag. 31 | *.js # When generated by dart2js. Don't specify *.js if your 32 | # project includes source files written in JavaScript. 33 | *.js_ 34 | *.js.deps 35 | *.js.map 36 | 37 | # End of https://www.gitignore.io/api/dart 38 | -------------------------------------------------------------------------------- /derry.yaml: -------------------------------------------------------------------------------- 1 | get: pub get 2 | format: dartfmt -w --set-exit-if-changed . 3 | analyze: dart analyze --fatal-infos --fatal-warnings . 4 | test: dart test 5 | test-coverage: 6 | - pub global activate test_coverage 7 | - pub global run test_coverage 8 | - genhtml coverage/lcov.info -o coverage/html 9 | example-http-server: 10 | - cd example/http_server && dartfmt -w --set-exit-if-changed . 11 | - cd example/http_server && dart analyze --fatal-infos --fatal-warnings . 12 | - cd example/http_server && dart compile aot-snapshot whatsapp_unilink_example.dart 13 | example-flutter: 14 | - cd example/flutter && flutter format --set-exit-if-changed . 15 | - cd example/flutter && flutter analyze --fatal-infos --fatal-warnings . 16 | - cd example/flutter && flutter build apk 17 | publish-dry-run: 18 | - pub publish --dry-run 19 | # Verify everything 20 | check: 21 | - $get 22 | - $format 23 | - $analyze 24 | - $test 25 | - $example-http-server 26 | - $example-flutter 27 | - $test-coverage 28 | - $publish-dry-run 29 | -------------------------------------------------------------------------------- /example/http_server/mailto_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:mailto/mailto.dart'; 4 | 5 | String renderHtml(Mailto mailto) => ''' 6 | 7 | 8 | mailto example 9 | 10 | 11 | Open mail client 12 | 13 | 14 | '''; 15 | 16 | Future main() async { 17 | final mailto = Mailto( 18 | to: [ 19 | 'example@example.com', 20 | 'ejemplo@ejemplo.com', 21 | ], 22 | cc: [ 23 | 'percentage%100@example.com', 24 | 'QuestionMark?address@example.com', 25 | ], 26 | bcc: [ 27 | 'Mike&family@example.org', 28 | ], 29 | subject: 'Let\'s drink a "café"! ☕️ 2+2=4 #coffeeAndMath', 30 | body: 31 | 'Hello this if the first line!\n\nNew line with some special characters őúóüűáéèßáñ\nEmoji: 🤪💙👍', 32 | ); 33 | 34 | final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 3000); 35 | 36 | print('Listening on http://localhost:${server.port}'); 37 | 38 | await for (HttpRequest request in server) { 39 | print('received request!'); 40 | request.response 41 | ..statusCode = HttpStatus.ok 42 | ..headers.contentType = ContentType.html 43 | ..write(renderHtml(mailto)); 44 | await request.response.close(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.0 2 | 3 | * Migrate package to Dart's null safety language feature, requiring Dart 2.12 or higher. Close [`mailto #7`](https://github.com/smaho-engineering/mailto/issues/7) 4 | * Make `to` values not required, do not depend on the `meta` package anymore. Fix [`mailto #4`](https://github.com/smaho-engineering/mailto/issues/4) 5 | 6 | ## 1.1.0 7 | 8 | * `to` values were made optional [`mailto #4`](https://github.com/smaho-engineering/mailto/issues/4) 9 | * removed the `validate` argument from `Mailto`. You may still validate paramaters using the `Mailto.validateParameters` static function. 10 | 11 | ## 1.0.0 12 | 13 | * Correctly encode trickier "to" arguments from the RFC linked in the README 14 | * Validate input in constructor and throw `ArgumentError` if something is incorrect 15 | * Make validation optional by setting the `validate` argument to `MailtoValidate.never 16 | * Clarify in README the limitations of mailto links 17 | * Add server example so it's easy to test on desktop/browser 18 | * Add screenshots about example Flutter app and server 19 | * README improvements 20 | * Fix pub.dev pub points 21 | 22 | ## 0.1.4 23 | 24 | Improve README 25 | 26 | ## 0.1.3 27 | 28 | Write README and Flutter example app 29 | 30 | ## 0.1.2 31 | 32 | Add the `meta` package as dependency 33 | 34 | ## 0.1.1 35 | 36 | Add changelog and example command-line script 37 | 38 | ## 0.1.0 39 | 40 | Initial version of the `mailto` package 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019, SMAHO GmbH. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /example/flutter/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/flutter/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 | mailto_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/flutter/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 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.mailto_example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/flutter/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 | -------------------------------------------------------------------------------- /example/flutter/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 | -------------------------------------------------------------------------------- /example/flutter/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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/mailto.dart: -------------------------------------------------------------------------------- 1 | /// [Mailto] helps you create email ("mailto") links. 2 | /// 3 | /// The class takes care of all the necessary encoding. 4 | /// 5 | /// **Flutter info** 6 | /// 7 | /// You can use the `url_launcher` package for launching the 8 | /// URL that the [Mailto] class's [toString] method returns. 9 | /// It will open the default email client on the smart phone, 10 | /// with pre-filled [to], [cc], [bcc] recipient list, [subject], and [body] 11 | /// (content of the email). 12 | /// 13 | /// The user can still decide not to send an email or edit any of the 14 | /// fields. 15 | class Mailto { 16 | /// Create a [Mailto] instance. 17 | /// 18 | /// Convert the instance to `String` in order to get the `mailto` URL 19 | /// corresponding to the instance. 20 | /// 21 | /// Fields aren't validated. Check [Mailto.validateParameters] for more info. 22 | Mailto({ 23 | this.to, 24 | this.cc, 25 | this.bcc, 26 | this.subject, 27 | this.body, 28 | }); 29 | 30 | /// Validate the incoming parameters whether they would be valid for 31 | /// a mailto link. 32 | /// 33 | /// In case the parameters don't pass validation, [ArgumentError] is thrown. 34 | /// 35 | /// The [Mailto] class does not validate its fields neither at instatiation 36 | /// nor when the toString method is called, so make sure that you either 37 | /// know that the values are valid and the mailto links work on devices, 38 | /// or call the `validateParameters` function in an `assert` call to catch 39 | /// issues during development. 40 | static void validateParameters({ 41 | List? to, 42 | List? cc, 43 | List? bcc, 44 | String? subject, 45 | String? body, 46 | }) { 47 | bool isEmptyString(String e) => e.isEmpty; 48 | bool containsLineBreak(String e) => e.contains('\n'); 49 | if (to?.any(isEmptyString) == true) { 50 | throw ArgumentError.value( 51 | to, 52 | 'to', 53 | 'elements in "to" list must not be empty', 54 | ); 55 | } 56 | if (to?.any(containsLineBreak) == true) { 57 | throw ArgumentError.value( 58 | to, 59 | 'to', 60 | 'elements in "to" list must not contain line breaks', 61 | ); 62 | } 63 | if (cc?.any(isEmptyString) == true) { 64 | throw ArgumentError.value( 65 | cc, 66 | 'cc', 67 | 'elements in "cc" list must not be empty. ', 68 | ); 69 | } 70 | if (cc?.any(containsLineBreak) == true) { 71 | throw ArgumentError.value( 72 | cc, 73 | 'cc', 74 | 'elements in "cc" list must not contain line breaks', 75 | ); 76 | } 77 | if (bcc?.any(isEmptyString) == true) { 78 | throw ArgumentError.value( 79 | bcc, 80 | 'bcc', 81 | 'elements in "bcc" list must not be empty. ', 82 | ); 83 | } 84 | if (bcc?.any(containsLineBreak) == true) { 85 | throw ArgumentError.value( 86 | bcc, 87 | 'bcc', 88 | 'elements in "bcc" list must not contain line breaks', 89 | ); 90 | } 91 | if (subject?.contains('\n') == true) { 92 | throw ArgumentError.value( 93 | subject, 94 | 'subject', 95 | '"subject" must not contain line breaks', 96 | ); 97 | } 98 | } 99 | 100 | /// Main recipient(s) of your email 101 | /// 102 | /// Destination email addresses. 103 | final List? to; 104 | 105 | /// Recipient(s) of a copy of the email. 106 | /// 107 | /// CC stands for carbon copy. When you CC people on an email, the CC list is 108 | /// visible to all other recipients. 109 | final List? cc; 110 | 111 | /// Recipient(s) of a secret copy of the email. 112 | /// 113 | /// BCC stands for blind carbon copy. When you BCC people on an email, the 114 | /// BCC list is not visible to other recipients. 115 | final List? bcc; 116 | 117 | /// Subject of email. 118 | final String? subject; 119 | 120 | /// Body of email. 121 | /// 122 | /// The content of the email. 123 | /// 124 | /// Please be aware that not all email clients are able to handle 125 | /// line-breaks in the body. 126 | final String? body; 127 | 128 | /// Percent encoded value of the comma (',') character. 129 | /// 130 | /// ```dart 131 | /// Uri.encodeComponent(',') == _comma; // true 132 | /// ``` 133 | static const String _comma = '%2C'; 134 | 135 | String _encodeTo(String s) { 136 | final atSign = s.lastIndexOf('@'); 137 | return Uri.encodeComponent(s.substring(0, atSign)) + s.substring(atSign); 138 | } 139 | 140 | @override 141 | String toString() { 142 | // Use a string buffer as input is of unknown length. 143 | final stringBuffer = StringBuffer('mailto:'); 144 | if (to != null) stringBuffer.writeAll(to!.map(_encodeTo), _comma); 145 | // We need this flag to know whether we should use & or ? when creating 146 | // the string. 147 | var parameterAdded = false; 148 | final parameterMap = { 149 | 'subject': subject, 150 | 'body': body, 151 | 'cc': cc?.join(','), 152 | 'bcc': bcc?.join(','), 153 | }; 154 | for (final parameter in parameterMap.entries) { 155 | // Do not add key-value pair where the value is missing or empty 156 | if (parameter.value == null || parameter.value!.isEmpty) continue; 157 | // We don't need to encode the keys because all keys are under the 158 | // package's control currently and all of those keys are simple keys 159 | // without any special characters. 160 | // The values need to be encoded. 161 | // The RFC also mentions that the body should use '%0D%0A' for 162 | // line-breaks, however,we didn't find any difference between 163 | // '%0A' and '%0D%0A', so we keep it at '%0A'. 164 | stringBuffer 165 | ..write(parameterAdded ? '&' : '?') 166 | ..write(parameter.key) 167 | ..write('=') 168 | ..write(Uri.encodeComponent(parameter.value!)); 169 | parameterAdded = true; 170 | } 171 | return stringBuffer.toString(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `mailto` 2 | 3 | > Simple Dart package for creating mailto links in your Flutter and Dart apps 4 | 5 | The `mailto` package helps you build mailto links and provides you with an idiomatic Dart interface that: 6 | 7 | * supports one or many `to`, `cc`, and `bcc` fields 8 | * supports custom body and subject for the emails 9 | * encodes every value for your correctly 10 | * is blazingly fast ⚡️😜 11 | 12 | [![smaho-engineering/mailto](https://img.shields.io/static/v1?label=smaho-engineering&message=mailto&logo=flutter&logoWidth=30&color=FF8200&labelColor=08589c&labelWidth=30)](https://github.com/smaho-engineering) 13 | 14 | [![Build Status](https://travis-ci.org/smaho-engineering/mailto.svg?branch=master)](https://travis-ci.org/smaho-engineering/mailto 'Check build status on TravisCI') [![Code coverage](https://img.shields.io/codecov/c/github/smaho-engineering/mailto.svg)](https://codecov.io/gh/smaho-engineering/mailto 'Check coverage info') 15 | 16 | [![mailto](https://img.shields.io/pub/v/mailto?label=mailto&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAeGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAAqACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAAAQdIdCAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjY0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjY0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ck0aSxoAAAaTSURBVFgJrVdbbBRVGP7OzOzsbmsXChIIiEQFRaIRhEKi0VRDjI++LIoPeHkhgRgeBCUCYY3iHTWGVHnxFhNpy6MXkMtCfLAENAGEAMGEgEBSLu1u2+3u7Mw5fv/MbrsFeiOeZHfOnMv/f//3X84ZYLytrc0e2HImOx8n9/yFv/d4OHtg08B4JmMN9P+3jjEK2axTkadwav8mnNxbxpmswbFdGv92GJzObgvnDRTGCEKNCaBYvWxZEK49/tsiOFYL6pJNyPUABgHVWTAmQOMEByWvBXOaV0dACFopM5KOkamqWi3K29I2Tu/LUHkHHKcJ3XmfgsVWcYkoctCV8xF3V+HM/pZQaaR8RCOHnzTGolAdCjqxbzFV0OrEwshqWqvUYCyEiyp/2viYMslBf+l9zHnyLTJjc23EXu26Sv/WDFSVm+0xnM++AxcdSNoL0dfjI8adrmWHzxjxy3v4rPTjBNab46C3Crldk0Ll24/Iqlu2mxmoKv/p93th+ndicnwBevp8aKOHtfpm0T7q3ThKzutY2vxpOJ0ho5vFZUNj4kYA8h4FTfsfHWj0luCHXBETVZwuAMQhN+4Ipd/4x0V+WWHGFI3ZDx5m/zMsn9YarhIgmYprOTDUBZls5Nf1f25AsW4JZhU8pB0nXFVP1Q38yXPUH6M/xYztyRl4pSWoS+1A+7WvIgBULiAqbaCDNFMt85SPrYceQUxvRpF+LKkY7rEcPG0H6CUzwoDwI8/RfkJV2bNw/YqHvm4fbnIlWju/C/UKAxUQVQAK7WkRydhhjjsxCRpGLi3x2LuPIJYSRKHinjG5gfuUUsh3CasW8td8JOpXoPXqt3xH6AaCiACE1DM43j2yHrHkYygVmOOVNBNltwPCkCqbunt7FEpFA8t2kL9OEMmX0Hb1myoIa4D6LYcfgjIZ9Oc5R+WqYq2svF0QJIABaKGnW9gQSQ56CCKefJlMfB0NtJH6cE61wHbiCLyoyJgaALKyFgTFYm9go46jMh7ljawa2oQFlgzkCGDyVElBWR2BaJj8ClqvBVLtDLYcXodY4gmUmO/DVTgRXQtirDEhXu7ttVDs1wg9LmilWBGUCZ6z8F7HPI68jSIPFpkYzhrOhm28IMRoHTAYuymZ/ar8CAyRaftLWE4SRku9FvGjt/GACN1AFvJdikCkmtbKJwylpkHLwTZkgkirUGvX1/THA0Kyoa9gob/AbJDEG5RNBswGOK7o58xgiaxRNXx3PCCMjtwwcBZEBlvY1LQT5dJquHUcCS8QUUFiToYBOrz6aGYsIKo1IUc3+L7I5V5hwWJNlhK8cXEL8/U1xOuZ/UQqtxsBIxeSsbSxgBDqi/0WCr0EIG6ImoV2ue3w0rCxaRtBrEEipeAmJBsCh2FjjQ1CFEKjVUwxKNdFzYNHcgRlGX0fMrHoCxjvVWh9CiZm+cxcTfqkmMttdFQsIzFRdUO+m+dLKWJBrhgREZX/wbNazfz+0DPTm4qtlwMvdV7Tb4xf8Z2AkU2Ss4OxXNlffcgE4xr/ML2qFVPmwg3UOmeeRj3Pa2PODTpDFsgxyRtwhlRdWLFk9+zUxJ8fnzJdPZtIeU2xRDCVd8SAu3xaI7KElSog2T7QbsVEVJCAVKNGvM7Q3VyueELd2HgDPlH5+Ogvl7fGguDFCY6bmOi4ehYV5wNPX/E9nAs81RUFKdWp8GpYvSKEhtaC4Nlh79O2dowxd051UNcQnRGlQl6W3bKleZtt5232+QtH19jJ+OdeLs/0IGQeKFRgPB2YfFA2nQRzNiirfsma0DsRmKqLbC4OXCbU6WKA4422un9uJ3FnEehfWJT2DgtAUNEVVoa0L7947A3lxj4kiDCHBYhstPhPqwWM7vbL5nJQUmcCXxmjGS8V70rwMa0XpBps51L9B4dXLtiCE6pX5EsbEQAdrTK0LARx+eg6Zcc+8vI9JjpVo1wSAfIu6jRDo2h83UVWLgYeOnkIPWC5epqbtFNuonfy3WbuNvXopeascQ4cPABsbuYpNVojXxnqEBAvXDy+1orZH9eCqG6XsJTLgbAiQgPS4DPgXcsyTn297Xvl3a0z5z+bZs1pXzb4oTI0C6rSap90eYYkphmYO2Y8/InxvLVuwx3yKVYBz4corbxK3ZAsYbNilm0Fmk7iYaS1/6sMXplyYIjRowOQXQTRnk5rAfHjXfO3+p73pgpPNbkt8lOMOvmTj1SJPQnWMCEY81opyy73FQqOxm4R1XzwoMwNtP8ArtQKBPNf6YAAAAAASUVORK5CYII=)](https://pub.dev/packages/mailto 'See mailto package info on pub.dev') 17 | [![GitHub Stars Count](https://img.shields.io/github/stars/smaho-engineering/mailto?logo=github)](https://github.com/smaho-engineering/mailto 'Star our repository on GitHub!') 18 | 19 | ## Important links 20 | 21 | * [Read the source code and **star the repo!**](https://github.com/smaho-engineering/mailto) 22 | * [Check package info on `pub.dev`](https://pub.dev/packages/mailto) 23 | * [Open an issue](https://github.com/smaho-engineering/mailto/issues) 24 | * [Read the docs](https://pub.dev/documentation/mailto/latest/) 25 | * This Dart package is created by the [SMAHO development team](https://github.com/smaho-engineering) 26 | 27 | ## Usage 28 | 29 | You may want to launch the email client on your user's phone with certain fields pre-filled. 30 | For Flutter apps, it's recommended to use the [`url_launcher`](https://pub.dev/packages/url_launcher) package for launching the links you create with the `mailto` package. 31 | 32 | ```dart 33 | import 'package:mailto/mailto.dart'; 34 | // For Flutter applications, you'll most likely want to use 35 | // the url_launcher package. 36 | import 'package:url_launcher/url_launcher.dart'; 37 | 38 | // ...somewhere in your Flutter app... 39 | launchMailto() async { 40 | final mailtoLink = Mailto( 41 | to: ['to@example.com'], 42 | cc: ['cc1@example.com', 'cc2@example.com'], 43 | subject: 'mailto example subject', 44 | body: 'mailto example body', 45 | ); 46 | // Convert the Mailto instance into a string. 47 | // Use either Dart's string interpolation 48 | // or the toString() method. 49 | await launch('$mailtoLink'); 50 | } 51 | ``` 52 | 53 | ### Validation 54 | 55 | The package provides a simple validation function. 56 | You could use this function in an `assert` to catch issues in development mode. 57 | 58 | The package doesn't validate automatically, so either use the validation function or make sure that the parameters you use are correct. 59 | 60 | ```dart 61 | Mailto.validateParameters( 62 | // New lines are NOT supported in subject lines 63 | subject: 'new lines in subject \n FTW', 64 | // What does this even mean? 65 | cc: ['\n\n\n', ''], 66 | ); 67 | ``` 68 | 69 | ## Known limitations of `mailto` URIs 70 | 71 | I tested the package manually in Flutter apps on iOS and Android (Gmail, FastMail, Yahoo email client), and in the browser on macOS, 72 | and the package has an extensive test suite that incorporates many examples from the [RFC 6068 - The 'mailto' URI Scheme](https://tools.ietf.org/html/rfc6068) document. 73 | 74 | Unfortunately, each client handle mailto links differently: Gmail does not add line-breaks in the message `body`, 75 | FastMail skips the `bcc`, Yahoo is not able to handle encoded values in `subject` and `body`, and these are only the three clients I tested. 76 | The iOS email client seems to handle everything well, so 🎸🤘🎉. 77 | 78 | The package might also not work if the resulting mailto links are extremely long. I don't know the exact character count where the links fail, but I'd try to keep things under 1000 characters. 79 | 80 | **Important**: Make sure you understand these limitations before you decide to incorporate `mailto` links in your app: 81 | letting users open their email clients with pre-filled values is a quick and easy way to let your users get in touch with you 82 | with extremely little development effort. At the same time, you need to keep in mind that it's very unlikely 83 | that these links are going to work consistently for all of your users. 84 | 85 | **If you need something bullet-proof, this package is not the right tool for solving your problem, so please consider alternative solutions (e.g. Flutter forms and a working backend).** 86 | 87 | In case you find potential improvements to the package, please create a pull request or let's discuss it in an issue. 88 | I might not merge all pull requests, especially changes that improve things for one client, but makes it worse for others. 89 | We consider the iOS mail app and Gmail on Android the two most important mail clients. 90 | 91 | ## Examples 92 | 93 | You'll find runnable, great examples on the project's GitHub repository in the [**`/example` folder**](https://github.com/smaho-engineering/mailto/tree/master/example). 94 | 95 | ### Flutter example app 96 | 97 | 1. Clone the repository 98 | 1. Change directory to `cd example/flutter` 99 | 1. `flutter run` and wait for the app to start 100 | 1. You can fill out the forms with your own input or click the "Surprise me" button to see how your mail client handles tricky input 101 | 102 | ### HTTP server serving an HTML web page with a mailto link 103 | 104 | The `mailto` package works in any Dart program: be it Flutter, AngularDart, or on the server. 105 | 106 | ```dart 107 | import 'dart:io'; 108 | 109 | import 'package:mailto/mailto.dart'; 110 | 111 | Future main() async { 112 | final mailto = Mailto( 113 | to: [ 114 | 'example@example.com', 115 | 'ejemplo@ejemplo.com', 116 | ], 117 | cc: [ 118 | 'percentage%100@example.com', 119 | 'QuestionMark?address@example.com', 120 | ], 121 | bcc: [ 122 | 'Mike&family@example.org', 123 | ], 124 | subject: 'Let\'s drink a "café"! ☕️ 2+2=4 #coffeeAndMath', 125 | body: 126 | 'Hello this if the first line!\n\nNew line with some special characters őúóüűáéèßáñ\nEmoji: 🤪💙👍', 127 | ); 128 | 129 | final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 3000); 130 | String renderHtml(Mailto mailto) => '''mailto exampleOpen mail client'''; 131 | await for (HttpRequest request in server) { 132 | request.response 133 | ..statusCode = HttpStatus.ok 134 | ..headers.contentType = ContentType.html 135 | ..write(renderHtml(mailto)); 136 | await request.response.close(); 137 | } 138 | } 139 | ``` 140 | 141 | 1. Clone the repository 142 | 1. Change directory to `cd example/http_server` 143 | 1. Start HTTP server `dart main.dart` 144 | 1. Open your browser and visit `localhost:3000` 145 | 1. Click on the link 146 | 1. If you have an email client installed on your computer, this client will be opened when you click the link on the HTML page. 147 | 148 | #### Screenshots 149 | 150 | mailto demo: Dart server HTML 151 | 152 | mailto demo: MacOS email client 153 | 154 | -------------------------------------------------------------------------------- /test/mailto_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mailto/mailto.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void _test(String description, Mailto builder, String result) { 5 | test(description, () { 6 | expect('$builder', result); 7 | }); 8 | } 9 | 10 | const _email = 'johndoe@example.com'; 11 | 12 | void main() { 13 | group('$Mailto', () { 14 | group('validateInput', () { 15 | group('"to"', () { 16 | test('can be empty', () { 17 | expect( 18 | () => Mailto.validateParameters(to: []), 19 | returnsNormally, 20 | ); 21 | }); 22 | 23 | test('elements in "to" list must not be empty', () { 24 | expect( 25 | () => Mailto.validateParameters(to: ['']), 26 | throwsA(isA()), 27 | ); 28 | }); 29 | 30 | test('elements of the "to" list must not contain line breaks', () { 31 | expect( 32 | () => Mailto.validateParameters(to: ['new line\nshould throw']), 33 | throwsA(isA()), 34 | ); 35 | }); 36 | 37 | test('valid', () { 38 | expect( 39 | () => Mailto.validateParameters(to: ['example@example.com']), 40 | isNot(throwsA(isA())), 41 | ); 42 | }); 43 | }); 44 | 45 | group('"cc"', () { 46 | const to = ['example@example.com']; 47 | test('may be empty list', () { 48 | expect( 49 | () => Mailto.validateParameters(to: to, cc: []), 50 | isNot(throwsA(isA())), 51 | ); 52 | }); 53 | 54 | test('elements in "cc" list must not be empty', () { 55 | expect( 56 | () => Mailto.validateParameters( 57 | to: to, 58 | cc: ['example2@example.com', ''], 59 | ), 60 | throwsA(isA()), 61 | ); 62 | }); 63 | 64 | test('elements of the "cc" list must not contain line breaks', () { 65 | expect( 66 | () => Mailto.validateParameters( 67 | to: to, 68 | cc: ['something\n@example.com'], 69 | ), 70 | throwsA(isA()), 71 | ); 72 | }); 73 | 74 | test('valid "cc"', () { 75 | expect( 76 | () => Mailto.validateParameters( 77 | to: to, 78 | cc: ['cc@example.com'], 79 | ), 80 | isNot(throwsA(isA())), 81 | ); 82 | }); 83 | }); 84 | 85 | group('"bcc"', () { 86 | const to = ['example@example.com']; 87 | test('may be empty list', () { 88 | expect( 89 | () => Mailto.validateParameters(to: to, bcc: []), 90 | isNot(throwsA(isA())), 91 | ); 92 | }); 93 | 94 | test('elements in "bcc" list must not be empty', () { 95 | expect( 96 | () => Mailto.validateParameters( 97 | to: to, 98 | bcc: ['example2@example.com', ''], 99 | ), 100 | throwsA(isA()), 101 | ); 102 | }); 103 | 104 | test('elements of the "bcc" list must not contain line breaks', () { 105 | expect( 106 | () => Mailto.validateParameters( 107 | to: to, 108 | bcc: ['something\n@example.com'], 109 | ), 110 | throwsA(isA()), 111 | ); 112 | }); 113 | 114 | test('valid "bcc"', () { 115 | expect( 116 | () => Mailto.validateParameters( 117 | to: to, 118 | bcc: ['cc@example.com'], 119 | ), 120 | isNot(throwsA(isA())), 121 | ); 122 | }); 123 | }); 124 | 125 | group('"subject"', () { 126 | const to = ['example@example.com']; 127 | test('must not contain line breaks', () { 128 | expect( 129 | () => Mailto.validateParameters( 130 | to: to, 131 | subject: 'Subject example\nwith new lines', 132 | ), 133 | throwsA(isA()), 134 | ); 135 | }); 136 | 137 | test('valid "subject"', () { 138 | expect( 139 | () => Mailto.validateParameters( 140 | to: to, 141 | subject: 'Valid subject', 142 | ), 143 | isNot(throwsA(isA())), 144 | ); 145 | }); 146 | }); 147 | 148 | group('"body"', () { 149 | const to = ['example@example.com']; 150 | test('may be null', () { 151 | expect( 152 | () => Mailto.validateParameters(to: to, body: null), 153 | isNot(throwsA(isA())), 154 | ); 155 | }); 156 | 157 | test('may contain line breaks', () { 158 | expect( 159 | () => Mailto.validateParameters( 160 | to: to, 161 | body: 'Body example\nwith new lines', 162 | ), 163 | isNot(throwsA(isA())), 164 | ); 165 | }); 166 | }); 167 | }); 168 | 169 | group('"to"', () { 170 | void _testTo(String description, List? to, String result) { 171 | _test(description, Mailto(to: to), result); 172 | } 173 | 174 | _testTo( 175 | 'contains one simple email', 176 | ['example@example.com'], 177 | 'mailto:example@example.com', 178 | ); 179 | 180 | _testTo( 181 | 'contains two simple emails', 182 | ['example@example.com', 'ejemplo@ejemplo.es'], 183 | 'mailto:example@example.com%2Cejemplo@ejemplo.es', 184 | ); 185 | 186 | // https://tools.ietf.org/html/rfc6068#section-6.1 187 | _testTo( 188 | 'address contains percentage (%) sign', 189 | ['gorby%kremvax@example.com'], 190 | 'mailto:gorby%25kremvax@example.com', 191 | ); 192 | 193 | // https://tools.ietf.org/html/rfc6068#section-6.1 194 | _testTo( 195 | 'address contains question mark (?) sign', 196 | ['unlikely?address@example.com'], 197 | 'mailto:unlikely%3Faddress@example.com', 198 | ); 199 | 200 | // https://tools.ietf.org/html/rfc6068#section-6.1 201 | _testTo( 202 | 'address contains ampersand (&) sign', 203 | ['Mike&family@example.org'], 204 | 'mailto:Mike%26family@example.org', 205 | ); 206 | 207 | // https://tools.ietf.org/html/rfc6068#section-6.2 208 | _testTo( 209 | 'complicated email address 1', 210 | ['"not@me"@example.com'], 211 | 'mailto:%22not%40me%22@example.com', 212 | ); 213 | 214 | // https://tools.ietf.org/html/rfc6068#section-6.2 215 | _testTo( 216 | 'complicated email address 2', 217 | [r'"oh\\no"@example.org'], 218 | 'mailto:%22oh%5C%5Cno%22@example.org', 219 | ); 220 | 221 | // https://tools.ietf.org/html/rfc6068#section-6.2 222 | _testTo( 223 | 'complicated email address 3', 224 | [r'''"\\\"it's\ ugly\\\""@example.org'''], 225 | '''mailto:%22%5C%5C%5C%22it's%5C%20ugly%5C%5C%5C%22%22@example.org''', 226 | ); 227 | 228 | _testTo( 229 | 'null safety', 230 | null, 231 | 'mailto:', 232 | ); 233 | }); 234 | 235 | group('missing "to"', () { 236 | _test( 237 | 'to can be null', 238 | Mailto(subject: 'Subject'), 239 | 'mailto:?subject=Subject', 240 | ); 241 | 242 | _test( 243 | 'to can be empty list', 244 | Mailto(to: [], subject: 'Subject'), 245 | 'mailto:?subject=Subject', 246 | ); 247 | }); 248 | 249 | group('"subject"', () { 250 | const email = 'subject@example.com'; 251 | const to = [email]; 252 | 253 | void _testSubject(String description, String subject, String result) { 254 | _test(description, Mailto(to: to, subject: subject), result); 255 | } 256 | 257 | _testSubject( 258 | 'is one word', 259 | 'Word', 260 | 'mailto:subject@example.com?subject=Word', 261 | ); 262 | 263 | _testSubject( 264 | 'is multiple words', 265 | 'Multiple words subject', 266 | 'mailto:subject@example.com?subject=Multiple%20words%20subject', 267 | ); 268 | 269 | _testSubject( 270 | 'contains & ampersand', 271 | 'Ampers & Sands Co.', 272 | 'mailto:subject@example.com?subject=Ampers%20%26%20Sands%20Co.', 273 | ); 274 | 275 | _testSubject( 276 | 'contains + plus', 277 | '2+2', 278 | 'mailto:subject@example.com?subject=2%2B2', 279 | ); 280 | 281 | _testSubject( 282 | 'contains = equals sign', 283 | '=3', 284 | 'mailto:subject@example.com?subject=%3D3', 285 | ); 286 | }); 287 | }); 288 | 289 | group( 290 | '$Mailto legacy tests', 291 | () { 292 | _test( 293 | 'Only "to" address is set', 294 | Mailto(to: [_email]), 295 | 'mailto:$_email', 296 | ); 297 | 298 | _test( 299 | 'Multiple to addresses', 300 | Mailto( 301 | to: [_email, 'vince@example.com'], 302 | subject: 'Hello Multiple', 303 | body: 'Mutliple to addresses', 304 | ), 305 | 'mailto:$_email%2Cvince@example.com?subject=Hello%20Multiple&body=Mutliple%20to%20addresses', 306 | ); 307 | 308 | _test( 309 | 'Spaces, commas, and new lines in the "body"', 310 | Mailto( 311 | to: [_email], 312 | subject: 'Lorem Ipsum', 313 | body: 314 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', 315 | ), 316 | 'mailto:$_email?subject=Lorem%20Ipsum&body=Lorem%20ipsum%20dolor%20sit%20amet%2C%20consectetur%20adipiscing%20elit%2C%20sed%20do%20eiusmod%20tempor%20incididunt%20ut%20labore%20et%20dolore%20magna%20aliqua.%0AUt%20enim%20ad%20minim%20veniam%2C%20quis%20nostrud%20exercitation%20ullamco%20laboris%20nisi%20ut%20aliquip%20ex%20ea%20commodo%20consequat.', 317 | ); 318 | 319 | _test( 320 | 'One address in "cc"', 321 | Mailto( 322 | to: [_email], 323 | subject: 'Multiple CCs', 324 | body: 'Multiple CC body', 325 | cc: [ 326 | 'vince@example.com', 327 | ], 328 | ), 329 | 'mailto:$_email?subject=Multiple%20CCs&body=Multiple%20CC%20body&cc=vince%40example.com', 330 | ); 331 | 332 | _test( 333 | 'Multiple "cc" addresses are joined as encoded commas', 334 | Mailto( 335 | to: [_email], 336 | subject: 'Multiple CCs', 337 | body: 'Multiple CC body', 338 | cc: [ 339 | 'vince@example.com', 340 | 'vicente@ejemplo.es', 341 | ], 342 | ), 343 | 'mailto:$_email?subject=Multiple%20CCs&body=Multiple%20CC%20body&cc=vince%40example.com%2Cvicente%40ejemplo.es', 344 | ); 345 | 346 | _test( 347 | 'Empty "cc" list is skipped', 348 | Mailto( 349 | to: [_email], 350 | subject: 'EmptyCC', 351 | body: 'EmptyCCBody', 352 | cc: [], 353 | ), 354 | 'mailto:$_email?subject=EmptyCC&body=EmptyCCBody', 355 | ); 356 | 357 | _test( 358 | 'Everything is set', 359 | Mailto( 360 | to: [_email], 361 | subject: 'cR@zY 3#4mPL3 with empty b0dy', 362 | body: '', 363 | cc: [ 364 | 'vince@example.com', 365 | 'vicente@ejemplo.es', 366 | ], 367 | bcc: [ 368 | 'secret@example.com', 369 | ], 370 | ), 371 | 'mailto:$_email?subject=cR%40zY%203%234mPL3%20with%20empty%20b0dy&cc=vince%40example.com%2Cvicente%40ejemplo.es&bcc=secret%40example.com', 372 | ); 373 | }, 374 | ); 375 | } 376 | -------------------------------------------------------------------------------- /example/flutter/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:mailto/mailto.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | void main() => runApp(MailtoExampleApp()); 6 | 7 | class MailtoExampleApp extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return CupertinoApp( 11 | debugShowCheckedModeBanner: false, 12 | theme: CupertinoThemeData( 13 | textTheme: CupertinoTextThemeData( 14 | navLargeTitleTextStyle: TextStyle( 15 | fontWeight: FontWeight.bold, 16 | fontSize: 22.0, 17 | color: CupertinoColors.activeGreen, 18 | ), 19 | ), 20 | ), 21 | home: MailtoDemo(), 22 | ); 23 | } 24 | } 25 | 26 | const Color white = Color(0xFFFFFFFF); 27 | const EdgeInsets textFieldPadding = EdgeInsets.all(16); 28 | const EdgeInsets iconPadding = EdgeInsets.fromLTRB(12, 0, 12, 0); 29 | 30 | class MailtoDemo extends StatefulWidget { 31 | @override 32 | _MailtoDemoState createState() => _MailtoDemoState(); 33 | } 34 | 35 | class _MailtoDemoState extends State { 36 | List to = []; 37 | List cc = []; 38 | List bcc = []; 39 | String subject = ''; 40 | String body = ''; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return CupertinoPageScaffold( 45 | navigationBar: CupertinoNavigationBar( 46 | backgroundColor: white, 47 | middle: Text( 48 | 'mailto', 49 | ), 50 | ), 51 | child: ListView( 52 | children: [ 53 | Padding( 54 | padding: const EdgeInsets.symmetric( 55 | horizontal: 12, 56 | ), 57 | child: CupertinoButton( 58 | color: Color(0xFF8E44AD), 59 | child: Text('Suprise me!'), 60 | onPressed: () async { 61 | final url = Mailto( 62 | to: [ 63 | 'example@example.com', 64 | 'ejemplo@ejemplo.com', 65 | ], 66 | cc: [ 67 | 'percentage%100@example.com', 68 | 'QuestionMark?address@example.com', 69 | ], 70 | bcc: [ 71 | 'Mike&family@example.org', 72 | ], 73 | subject: 'Let\'s drink a "café"! ☕️ 2+2=4 #coffeeAndMath', 74 | body: 75 | 'Hello this if the first line!\n\nNew line with some special characters őúóüűáéèßáñ\nEmoji: 🤪💙👍', 76 | ).toString(); 77 | if (await canLaunch(url)) { 78 | await launch(url); 79 | } else { 80 | showCupertinoDialog( 81 | context: context, 82 | builder: MailClientOpenErrorDialog(url: url).build, 83 | ); 84 | } 85 | }, 86 | ), 87 | ), 88 | EmailsContainer( 89 | onChanged: (v) => to = v, 90 | icon: CupertinoIcons.person_solid, 91 | title: 'to', 92 | placeholder: 'Recipient', 93 | ), 94 | EmailsContainer( 95 | onChanged: (v) => cc = v, 96 | icon: CupertinoIcons.group_solid, 97 | title: 'cc', 98 | placeholder: 'Carbon-copy', 99 | ), 100 | EmailsContainer( 101 | onChanged: (v) => bcc = v, 102 | icon: CupertinoIcons.group, 103 | title: 'bcc', 104 | placeholder: 'Blind Carbon-copy', 105 | ), 106 | SubjectTextField( 107 | onChanged: (v) => subject = v, 108 | ), 109 | BodyTextField( 110 | onChanged: (v) => body = v, 111 | ), 112 | Padding( 113 | padding: const EdgeInsets.symmetric( 114 | vertical: 32, 115 | horizontal: 12, 116 | ), 117 | child: CupertinoButton.filled( 118 | child: Text('Open Mail Client'), 119 | onPressed: () async { 120 | final url = Mailto( 121 | to: to, 122 | cc: cc, 123 | bcc: bcc, 124 | subject: subject, 125 | body: body, 126 | ).toString(); 127 | if (await canLaunch(url)) { 128 | await launch(url); 129 | } else { 130 | showCupertinoDialog( 131 | context: context, 132 | builder: MailClientOpenErrorDialog(url: url).build, 133 | ); 134 | } 135 | }, 136 | ), 137 | ), 138 | ], 139 | ), 140 | ); 141 | } 142 | } 143 | 144 | class MailClientOpenErrorDialog extends StatelessWidget { 145 | final String url; 146 | 147 | const MailClientOpenErrorDialog({Key? key, required this.url}) 148 | : assert(url != ''), 149 | super(key: key); 150 | 151 | @override 152 | Widget build(BuildContext context) { 153 | return CupertinoAlertDialog( 154 | title: Text('Launch Error'), 155 | content: Text('We could not launch the following url:\n$url'), 156 | actions: [ 157 | CupertinoDialogAction( 158 | isDefaultAction: true, 159 | child: Text('OK'), 160 | onPressed: () { 161 | Navigator.of(context).pop(); 162 | }, 163 | ), 164 | ], 165 | ); 166 | } 167 | } 168 | 169 | class BodyTextField extends StatefulWidget { 170 | final ValueChanged onChanged; 171 | 172 | const BodyTextField({ 173 | Key? key, 174 | required this.onChanged, 175 | }) : super(key: key); 176 | 177 | @override 178 | _BodyTextFieldState createState() => _BodyTextFieldState(); 179 | } 180 | 181 | class _BodyTextFieldState extends State { 182 | final TextEditingController _controller = TextEditingController(); 183 | 184 | bool isEnabled = false; 185 | 186 | @override 187 | void dispose() { 188 | _controller.dispose(); 189 | super.dispose(); 190 | } 191 | 192 | @override 193 | Widget build(BuildContext context) { 194 | return Column( 195 | mainAxisSize: MainAxisSize.min, 196 | children: [ 197 | SectionHeading( 198 | leadingIcon: CupertinoIcons.book, 199 | title: 'body', 200 | trailingIcon: 201 | isEnabled ? CupertinoIcons.delete : CupertinoIcons.add_circled, 202 | onPressed: () { 203 | setState(() => isEnabled = !isEnabled); 204 | _controller.text = ''; 205 | if (isEnabled) { 206 | widget.onChanged(_controller.text); 207 | } 208 | }, 209 | ), 210 | if (isEnabled) 211 | Padding( 212 | padding: const EdgeInsets.fromLTRB(12, 4, 12, 4), 213 | child: CupertinoTextField( 214 | controller: _controller, 215 | padding: textFieldPadding, 216 | minLines: 4, 217 | maxLines: null, 218 | textCapitalization: TextCapitalization.sentences, 219 | keyboardType: TextInputType.multiline, 220 | placeholder: 'Body of the email', 221 | onChanged: widget.onChanged, 222 | ), 223 | ), 224 | ], 225 | ); 226 | } 227 | } 228 | 229 | class SectionHeading extends StatelessWidget { 230 | final String title; 231 | final VoidCallback onPressed; 232 | final IconData leadingIcon; 233 | final IconData trailingIcon; 234 | 235 | const SectionHeading({ 236 | Key? key, 237 | required this.title, 238 | required this.onPressed, 239 | required this.leadingIcon, 240 | required this.trailingIcon, 241 | }) : super(key: key); 242 | 243 | @override 244 | Widget build(BuildContext context) { 245 | return Flexible( 246 | child: Row( 247 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 248 | children: [ 249 | Row( 250 | children: [ 251 | Padding( 252 | padding: iconPadding, 253 | child: Icon(leadingIcon), 254 | ), 255 | LargeText(title), 256 | ], 257 | ), 258 | Padding( 259 | padding: const EdgeInsets.only(right: 4), 260 | child: CupertinoButton( 261 | child: Icon(trailingIcon), 262 | onPressed: onPressed, 263 | ), 264 | ), 265 | ], 266 | ), 267 | ); 268 | } 269 | } 270 | 271 | class SubjectTextField extends StatefulWidget { 272 | final ValueChanged onChanged; 273 | 274 | const SubjectTextField({ 275 | Key? key, 276 | required this.onChanged, 277 | }) : super(key: key); 278 | 279 | @override 280 | _SubjectTextFieldState createState() => _SubjectTextFieldState(); 281 | } 282 | 283 | class _SubjectTextFieldState extends State { 284 | final TextEditingController _controller = TextEditingController(); 285 | 286 | bool isEnabled = false; 287 | 288 | @override 289 | void dispose() { 290 | _controller.dispose(); 291 | super.dispose(); 292 | } 293 | 294 | @override 295 | Widget build(BuildContext context) { 296 | return Column( 297 | mainAxisSize: MainAxisSize.min, 298 | children: [ 299 | SectionHeading( 300 | leadingIcon: CupertinoIcons.mail, 301 | title: 'subject', 302 | trailingIcon: 303 | isEnabled ? CupertinoIcons.delete : CupertinoIcons.add_circled, 304 | onPressed: () { 305 | setState(() => isEnabled = !isEnabled); 306 | _controller.text = ''; 307 | if (isEnabled) { 308 | widget.onChanged(_controller.text); 309 | } 310 | }, 311 | ), 312 | if (isEnabled) 313 | Padding( 314 | padding: const EdgeInsets.fromLTRB(12, 4, 12, 4), 315 | child: CupertinoTextField( 316 | controller: _controller, 317 | padding: textFieldPadding, 318 | textCapitalization: TextCapitalization.words, 319 | placeholder: 'Subject of the email', 320 | onChanged: widget.onChanged, 321 | ), 322 | ), 323 | ], 324 | ); 325 | } 326 | } 327 | 328 | class LargeText extends StatelessWidget { 329 | final String data; 330 | 331 | const LargeText(this.data, {Key? key}) : super(key: key); 332 | 333 | @override 334 | Widget build(BuildContext context) { 335 | return Padding( 336 | padding: const EdgeInsets.symmetric(vertical: 16), 337 | child: Text( 338 | data, 339 | style: CupertinoTheme.of(context) 340 | .textTheme 341 | .navLargeTitleTextStyle 342 | .copyWith(color: CupertinoColors.black), 343 | ), 344 | ); 345 | } 346 | } 347 | 348 | class EmailsContainer extends StatefulWidget { 349 | const EmailsContainer({ 350 | Key? key, 351 | required this.title, 352 | required this.icon, 353 | required this.onChanged, 354 | required this.placeholder, 355 | }) : super(key: key); 356 | 357 | final String title; 358 | final String placeholder; 359 | final IconData icon; 360 | final ValueChanged> onChanged; 361 | 362 | @override 363 | _EmailsContainerState createState() => _EmailsContainerState(); 364 | } 365 | 366 | class _EmailsContainerState extends State { 367 | final List _controllers = []; 368 | 369 | @override 370 | void dispose() { 371 | _controllers.forEach((c) => c.dispose()); 372 | super.dispose(); 373 | } 374 | 375 | @override 376 | Widget build(BuildContext context) { 377 | return Column( 378 | mainAxisSize: MainAxisSize.min, 379 | children: [ 380 | SectionHeading( 381 | leadingIcon: widget.icon, 382 | trailingIcon: CupertinoIcons.plus_circled, 383 | title: widget.title, 384 | onPressed: () { 385 | setState(() { 386 | _controllers.add(TextEditingController(text: '')); 387 | }); 388 | callOnChanged(); 389 | }, 390 | ), 391 | ...buildTextFields(), 392 | ], 393 | ); 394 | } 395 | 396 | void callOnChanged() { 397 | widget.onChanged(_controllers.map((c) => c.text.trim()).toList()); 398 | } 399 | 400 | void removeController(int index) { 401 | assert(index >= 0); 402 | assert(index < _controllers.length); 403 | setState(() => _controllers.removeAt(index)); 404 | callOnChanged(); 405 | } 406 | 407 | List buildTextFields() { 408 | final widgets = []; 409 | _controllers.asMap().forEach((index, controller) { 410 | widgets.add(Padding( 411 | padding: const EdgeInsets.fromLTRB(12, 4, 12, 4), 412 | child: CupertinoTextField( 413 | keyboardType: TextInputType.emailAddress, 414 | controller: controller, 415 | padding: textFieldPadding, 416 | suffix: CupertinoButton( 417 | padding: EdgeInsets.zero, 418 | onPressed: () => removeController(index), 419 | child: Padding( 420 | padding: const EdgeInsets.fromLTRB(16, 8, 8, 8), 421 | child: Icon(CupertinoIcons.minus_circled), 422 | ), 423 | ), 424 | textCapitalization: TextCapitalization.none, 425 | placeholder: '${widget.placeholder} ${index + 1}', 426 | onChanged: (_) => callOnChanged(), 427 | ), 428 | )); 429 | }); 430 | return widgets; 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /example/flutter/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 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 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 4CA115984980F1831515CAEA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E549DDF821F139FA1962ED84 /* Pods_Runner.framework */; }; 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 31 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 40 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 41 | 3A355644FF4C98F60ED24FC4 /* 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 = ""; }; 42 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 43 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 44 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 45 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 47 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 48 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 49 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 50 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 52 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 54 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 997AEA74ACAF1D5BFD863884 /* 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 = ""; }; 56 | ABF66EEEF39A9C8F4FEA5962 /* 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 = ""; }; 57 | E549DDF821F139FA1962ED84 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 66 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 67 | 4CA115984980F1831515CAEA /* Pods_Runner.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 5E8F23BD9A964756E0D3E6DE /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | E549DDF821F139FA1962ED84 /* Pods_Runner.framework */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | 64122CDE2D74A7B646D84C1F /* Pods */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 3A355644FF4C98F60ED24FC4 /* Pods-Runner.debug.xcconfig */, 86 | ABF66EEEF39A9C8F4FEA5962 /* Pods-Runner.release.xcconfig */, 87 | 997AEA74ACAF1D5BFD863884 /* Pods-Runner.profile.xcconfig */, 88 | ); 89 | name = Pods; 90 | path = Pods; 91 | sourceTree = ""; 92 | }; 93 | 9740EEB11CF90186004384FC /* Flutter */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 3B80C3931E831B6300D905FE /* App.framework */, 97 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 98 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 99 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 100 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 101 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 102 | ); 103 | name = Flutter; 104 | sourceTree = ""; 105 | }; 106 | 97C146E51CF9000F007C117D = { 107 | isa = PBXGroup; 108 | children = ( 109 | 9740EEB11CF90186004384FC /* Flutter */, 110 | 97C146F01CF9000F007C117D /* Runner */, 111 | 97C146EF1CF9000F007C117D /* Products */, 112 | 64122CDE2D74A7B646D84C1F /* Pods */, 113 | 5E8F23BD9A964756E0D3E6DE /* Frameworks */, 114 | ); 115 | sourceTree = ""; 116 | }; 117 | 97C146EF1CF9000F007C117D /* Products */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 97C146EE1CF9000F007C117D /* Runner.app */, 121 | ); 122 | name = Products; 123 | sourceTree = ""; 124 | }; 125 | 97C146F01CF9000F007C117D /* Runner */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 129 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 130 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 131 | 97C147021CF9000F007C117D /* Info.plist */, 132 | 97C146F11CF9000F007C117D /* Supporting Files */, 133 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 134 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 135 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 136 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 137 | ); 138 | path = Runner; 139 | sourceTree = ""; 140 | }; 141 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | ); 145 | name = "Supporting Files"; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | 97C146ED1CF9000F007C117D /* Runner */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 154 | buildPhases = ( 155 | 1687C6D088A4C2786F3242EC /* [CP] Check Pods Manifest.lock */, 156 | 9740EEB61CF901F6004384FC /* Run Script */, 157 | 97C146EA1CF9000F007C117D /* Sources */, 158 | 97C146EB1CF9000F007C117D /* Frameworks */, 159 | 97C146EC1CF9000F007C117D /* Resources */, 160 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 161 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 162 | 43EB83EAD49E6CEB0FB9B573 /* [CP] Embed Pods Frameworks */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = Runner; 169 | productName = Runner; 170 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 171 | productType = "com.apple.product-type.application"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | 97C146E61CF9000F007C117D /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastUpgradeCheck = 1020; 180 | ORGANIZATIONNAME = "The Chromium Authors"; 181 | TargetAttributes = { 182 | 97C146ED1CF9000F007C117D = { 183 | CreatedOnToolsVersion = 7.3.1; 184 | LastSwiftMigration = 1100; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 189 | compatibilityVersion = "Xcode 3.2"; 190 | developmentRegion = en; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = 97C146E51CF9000F007C117D; 197 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 97C146ED1CF9000F007C117D /* Runner */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | 97C146EC1CF9000F007C117D /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 212 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 213 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 214 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXShellScriptBuildPhase section */ 221 | 1687C6D088A4C2786F3242EC /* [CP] Check Pods Manifest.lock */ = { 222 | isa = PBXShellScriptBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | inputFileListPaths = ( 227 | ); 228 | inputPaths = ( 229 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 230 | "${PODS_ROOT}/Manifest.lock", 231 | ); 232 | name = "[CP] Check Pods Manifest.lock"; 233 | outputFileListPaths = ( 234 | ); 235 | outputPaths = ( 236 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | shellPath = /bin/sh; 240 | 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"; 241 | showEnvVarsInLog = 0; 242 | }; 243 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 244 | isa = PBXShellScriptBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | ); 248 | inputPaths = ( 249 | ); 250 | name = "Thin Binary"; 251 | outputPaths = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | shellPath = /bin/sh; 255 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 256 | }; 257 | 43EB83EAD49E6CEB0FB9B573 /* [CP] Embed Pods Frameworks */ = { 258 | isa = PBXShellScriptBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | inputPaths = ( 263 | ); 264 | name = "[CP] Embed Pods Frameworks"; 265 | outputPaths = ( 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | shellPath = /bin/sh; 269 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 270 | showEnvVarsInLog = 0; 271 | }; 272 | 9740EEB61CF901F6004384FC /* Run Script */ = { 273 | isa = PBXShellScriptBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputPaths = ( 278 | ); 279 | name = "Run Script"; 280 | outputPaths = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | shellPath = /bin/sh; 284 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 285 | }; 286 | /* End PBXShellScriptBuildPhase section */ 287 | 288 | /* Begin PBXSourcesBuildPhase section */ 289 | 97C146EA1CF9000F007C117D /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 294 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXVariantGroup section */ 301 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | 97C146FB1CF9000F007C117D /* Base */, 305 | ); 306 | name = Main.storyboard; 307 | sourceTree = ""; 308 | }; 309 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 97C147001CF9000F007C117D /* Base */, 313 | ); 314 | name = LaunchScreen.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 321 | isa = XCBuildConfiguration; 322 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_ANALYZER_NONNULL = YES; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_COMMA = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 336 | CLANG_WARN_EMPTY_BODY = YES; 337 | CLANG_WARN_ENUM_CONVERSION = YES; 338 | CLANG_WARN_INFINITE_RECURSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 352 | ENABLE_NS_ASSERTIONS = NO; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 363 | MTL_ENABLE_DEBUG_INFO = NO; 364 | SDKROOT = iphoneos; 365 | SUPPORTED_PLATFORMS = iphoneos; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Profile; 370 | }; 371 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | CLANG_ENABLE_MODULES = YES; 377 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 378 | ENABLE_BITCODE = NO; 379 | FRAMEWORK_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "$(PROJECT_DIR)/Flutter", 382 | ); 383 | INFOPLIST_FILE = Runner/Info.plist; 384 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 385 | LIBRARY_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "$(PROJECT_DIR)/Flutter", 388 | ); 389 | PRODUCT_BUNDLE_IDENTIFIER = com.example.mailtoExample; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 392 | SWIFT_VERSION = 5.0; 393 | VERSIONING_SYSTEM = "apple-generic"; 394 | }; 395 | name = Profile; 396 | }; 397 | 97C147031CF9000F007C117D /* Debug */ = { 398 | isa = XCBuildConfiguration; 399 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 400 | buildSettings = { 401 | ALWAYS_SEARCH_USER_PATHS = NO; 402 | CLANG_ANALYZER_NONNULL = YES; 403 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 404 | CLANG_CXX_LIBRARY = "libc++"; 405 | CLANG_ENABLE_MODULES = YES; 406 | CLANG_ENABLE_OBJC_ARC = YES; 407 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 408 | CLANG_WARN_BOOL_CONVERSION = YES; 409 | CLANG_WARN_COMMA = YES; 410 | CLANG_WARN_CONSTANT_CONVERSION = YES; 411 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 412 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 422 | CLANG_WARN_STRICT_PROTOTYPES = YES; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_UNREACHABLE_CODE = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 427 | COPY_PHASE_STRIP = NO; 428 | DEBUG_INFORMATION_FORMAT = dwarf; 429 | ENABLE_STRICT_OBJC_MSGSEND = YES; 430 | ENABLE_TESTABILITY = YES; 431 | GCC_C_LANGUAGE_STANDARD = gnu99; 432 | GCC_DYNAMIC_NO_PIC = NO; 433 | GCC_NO_COMMON_BLOCKS = YES; 434 | GCC_OPTIMIZATION_LEVEL = 0; 435 | GCC_PREPROCESSOR_DEFINITIONS = ( 436 | "DEBUG=1", 437 | "$(inherited)", 438 | ); 439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 441 | GCC_WARN_UNDECLARED_SELECTOR = YES; 442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 443 | GCC_WARN_UNUSED_FUNCTION = YES; 444 | GCC_WARN_UNUSED_VARIABLE = YES; 445 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 446 | MTL_ENABLE_DEBUG_INFO = YES; 447 | ONLY_ACTIVE_ARCH = YES; 448 | SDKROOT = iphoneos; 449 | TARGETED_DEVICE_FAMILY = "1,2"; 450 | }; 451 | name = Debug; 452 | }; 453 | 97C147041CF9000F007C117D /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 456 | buildSettings = { 457 | ALWAYS_SEARCH_USER_PATHS = NO; 458 | CLANG_ANALYZER_NONNULL = YES; 459 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 460 | CLANG_CXX_LIBRARY = "libc++"; 461 | CLANG_ENABLE_MODULES = YES; 462 | CLANG_ENABLE_OBJC_ARC = YES; 463 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 464 | CLANG_WARN_BOOL_CONVERSION = YES; 465 | CLANG_WARN_COMMA = YES; 466 | CLANG_WARN_CONSTANT_CONVERSION = YES; 467 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 468 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 469 | CLANG_WARN_EMPTY_BODY = YES; 470 | CLANG_WARN_ENUM_CONVERSION = YES; 471 | CLANG_WARN_INFINITE_RECURSION = YES; 472 | CLANG_WARN_INT_CONVERSION = YES; 473 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 475 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 476 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 477 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 478 | CLANG_WARN_STRICT_PROTOTYPES = YES; 479 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 483 | COPY_PHASE_STRIP = NO; 484 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 485 | ENABLE_NS_ASSERTIONS = NO; 486 | ENABLE_STRICT_OBJC_MSGSEND = YES; 487 | GCC_C_LANGUAGE_STANDARD = gnu99; 488 | GCC_NO_COMMON_BLOCKS = YES; 489 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 490 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 491 | GCC_WARN_UNDECLARED_SELECTOR = YES; 492 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 493 | GCC_WARN_UNUSED_FUNCTION = YES; 494 | GCC_WARN_UNUSED_VARIABLE = YES; 495 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 496 | MTL_ENABLE_DEBUG_INFO = NO; 497 | SDKROOT = iphoneos; 498 | SUPPORTED_PLATFORMS = iphoneos; 499 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 500 | TARGETED_DEVICE_FAMILY = "1,2"; 501 | VALIDATE_PRODUCT = YES; 502 | }; 503 | name = Release; 504 | }; 505 | 97C147061CF9000F007C117D /* Debug */ = { 506 | isa = XCBuildConfiguration; 507 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 508 | buildSettings = { 509 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 510 | CLANG_ENABLE_MODULES = YES; 511 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 512 | ENABLE_BITCODE = NO; 513 | FRAMEWORK_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "$(PROJECT_DIR)/Flutter", 516 | ); 517 | INFOPLIST_FILE = Runner/Info.plist; 518 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 519 | LIBRARY_SEARCH_PATHS = ( 520 | "$(inherited)", 521 | "$(PROJECT_DIR)/Flutter", 522 | ); 523 | PRODUCT_BUNDLE_IDENTIFIER = com.example.mailtoExample; 524 | PRODUCT_NAME = "$(TARGET_NAME)"; 525 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 526 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 527 | SWIFT_VERSION = 5.0; 528 | VERSIONING_SYSTEM = "apple-generic"; 529 | }; 530 | name = Debug; 531 | }; 532 | 97C147071CF9000F007C117D /* Release */ = { 533 | isa = XCBuildConfiguration; 534 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 535 | buildSettings = { 536 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 537 | CLANG_ENABLE_MODULES = YES; 538 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 539 | ENABLE_BITCODE = NO; 540 | FRAMEWORK_SEARCH_PATHS = ( 541 | "$(inherited)", 542 | "$(PROJECT_DIR)/Flutter", 543 | ); 544 | INFOPLIST_FILE = Runner/Info.plist; 545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 546 | LIBRARY_SEARCH_PATHS = ( 547 | "$(inherited)", 548 | "$(PROJECT_DIR)/Flutter", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = com.example.mailtoExample; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 553 | SWIFT_VERSION = 5.0; 554 | VERSIONING_SYSTEM = "apple-generic"; 555 | }; 556 | name = Release; 557 | }; 558 | /* End XCBuildConfiguration section */ 559 | 560 | /* Begin XCConfigurationList section */ 561 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 97C147031CF9000F007C117D /* Debug */, 565 | 97C147041CF9000F007C117D /* Release */, 566 | 249021D3217E4FDB00AE95B9 /* Profile */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 97C147061CF9000F007C117D /* Debug */, 575 | 97C147071CF9000F007C117D /* Release */, 576 | 249021D4217E4FDB00AE95B9 /* Profile */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | /* End XCConfigurationList section */ 582 | }; 583 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 584 | } 585 | --------------------------------------------------------------------------------