Skip to content

Commit 74fb98c

Browse files
committed
Refactor OpenMail functionality and add email composition models
- Simplified the openMailApp method to always simulate success on iOS. - Removed picker title behavior from openMailApp tests. - Introduced ComposeData and EmailContent models for structured email composition data. - Added MailApp model to encapsulate mail app details and composition data. - Implemented composeLaunchScheme method in MailApp for generating launch URLs. - Enhanced OpenMail class to support composing emails with content and attachments. - Updated tests to reflect changes in the OpenMail API and email composition logic.
1 parent 1028421 commit 74fb98c

File tree

14 files changed

+937
-982
lines changed

14 files changed

+937
-982
lines changed

android/src/main/kotlin/com/cuboid/open_mail/OpenMailAppPlugin.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class OpenMailAppPlugin : FlutterPlugin, MethodCallHandler {
3636

3737
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
3838
if (call.method == "openMailApp") {
39-
val opened = emailAppIntent(call.argument("nativePickerTitle") ?: "")
39+
// nativePickerTitle parameter removed; use default system chooser title (or none)
40+
val opened = emailAppIntent()
4041
result.success(opened)
4142
} else if (call.method == "openSpecificMailApp" && call.hasArgument("name")) {
4243
val opened = specificEmailAppIntent(call.argument("name")!!)
@@ -69,7 +70,7 @@ class OpenMailAppPlugin : FlutterPlugin, MethodCallHandler {
6970
channel.setMethodCallHandler(null)
7071
}
7172

72-
private fun emailAppIntent(@NonNull chooserTitle: String): Boolean {
73+
private fun emailAppIntent(): Boolean {
7374
val emailIntent = Intent(Intent.ACTION_VIEW, Uri.parse("mailto:"))
7475
val packageManager = applicationContext.packageManager
7576

@@ -78,7 +79,8 @@ class OpenMailAppPlugin : FlutterPlugin, MethodCallHandler {
7879
// use the first email package to create the chooserIntent
7980
val firstEmailPackageName = activitiesHandlingEmails.first().activityInfo.packageName
8081
val firstEmailInboxIntent = packageManager.getLaunchIntentForPackage(firstEmailPackageName)
81-
val emailAppChooserIntent = Intent.createChooser(firstEmailInboxIntent, chooserTitle)
82+
// No explicit title; system will use default
83+
val emailAppChooserIntent = Intent.createChooser(firstEmailInboxIntent, null)
8284

8385
// created UI for other email packages and add them to the chooser
8486
val emailInboxIntents = mutableListOf<LabeledIntent>()

example/android/app/build.gradle

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,23 @@ plugins {
77

88
android {
99
namespace = "com.open_mail.example"
10-
compileSdk = 35
11-
ndkVersion = "27.0.12077973"
10+
ndkVersion = '29.0.13846066'
1211

1312
compileOptions {
14-
sourceCompatibility = JavaVersion.VERSION_1_8
15-
targetCompatibility = JavaVersion.VERSION_1_8
13+
sourceCompatibility = JavaVersion.VERSION_11
14+
targetCompatibility = JavaVersion.VERSION_11
1615
}
1716

1817
kotlinOptions {
19-
jvmTarget = JavaVersion.VERSION_1_8
18+
jvmTarget = JavaVersion.VERSION_11
2019
}
2120

2221
defaultConfig {
23-
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
2422
applicationId = "com.open_mail.example"
25-
// You can update the following values to match your application needs.
26-
// For more information, see: https://flutter.dev/to/review-gradle-config.
2723
minSdkVersion 26
28-
targetSdk = 35
24+
compileSdk 36
25+
26+
targetSdk = 36
2927
versionCode = flutter.versionCode
3028
versionName = flutter.versionName
3129
}
@@ -37,6 +35,8 @@ android {
3735
signingConfig = signingConfigs.debug
3836
}
3937
}
38+
buildToolsVersion '36.0.0'
39+
compileSdk 36
4040
}
4141

4242
flutter {

example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Tue Sep 09 21:41:16 PKT 2025
12
distributionBase=GRADLE_USER_HOME
23
distributionPath=wrapper/dists
4+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
35
zipStoreBase=GRADLE_USER_HOME
46
zipStorePath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip

example/android/settings.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ pluginManagement {
1818

1919
plugins {
2020
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21-
id "com.android.application" version "8.3.2" apply false
22-
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
21+
id "com.android.application" version "8.11.1" apply false
22+
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
2323
}
2424

2525
include ":app"

example/ios/Runner/Info.plist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,20 @@
4545
<true/>
4646
<key>UIApplicationSupportsIndirectInputEvents</key>
4747
<true/>
48+
<!-- Allow querying of third-party mail URL schemes -->
49+
<key>LSApplicationQueriesSchemes</key>
50+
<array>
51+
<string>googlegmail</string>
52+
<string>x-dispatch</string>
53+
<string>readdle-spark</string>
54+
<string>airmail</string>
55+
<string>ms-outlook</string>
56+
<string>ymail</string>
57+
<string>fastmail</string>
58+
<string>superhuman</string>
59+
<string>protonmail</string>
60+
<!-- Include message for Apple Mail inbox scheme detection -->
61+
<string>message</string>
62+
</array>
4863
</dict>
4964
</plist>

example/lib/main.dart

Lines changed: 104 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
// ignore_for_file: use_build_context_synchronously
22

3+
import 'dart:developer';
4+
import 'dart:io';
5+
36
import 'package:flutter/material.dart';
47
import 'package:open_mail/open_mail.dart';
58

@@ -36,80 +39,29 @@ class MyApp extends StatelessWidget {
3639
ElevatedButton(
3740
child: const Text("Open Mail App"),
3841
onPressed: () async {
39-
//TODO: open default mail inbox only
40-
var result = await OpenMail.openMailApp(
41-
nativePickerTitle: 'Select email app to open',
42-
);
42+
var result = await OpenMail.openMailApp();
4343

4444
// If no mail apps are installed
4545
if (!result.didOpen && !result.canOpen) {
4646
showNoMailAppsDialog(context);
4747
}
48-
// If multiple mail apps are available on iOS, show a picker
49-
else if (!result.didOpen && result.canOpen) {
50-
showDialog(
51-
context: context,
52-
builder: (_) {
53-
return MailAppPickerDialog(
54-
mailApps: result.options,
55-
);
56-
},
57-
);
58-
}
59-
},
60-
),
61-
62-
// Button to test mail app detection
63-
ElevatedButton(
64-
child: const Text("Debug Mail App Detection (Custom Picker)"),
65-
onPressed: () async {
66-
try {
67-
var options = await OpenMail.getMailApps();
68-
showDialog(
69-
context: context,
70-
builder: (_) => MailAppPickerDialog(
71-
mailApps: options,
72-
itemBuilder: (context, app, fn) => ListTile(
73-
onTap: () async {
74-
// TODO: open specific mail app inbox only
75-
await OpenMail.openSpecificMailApp(
76-
app.name, emailContent);
77-
},
78-
leading:
79-
const Icon(Icons.email), // Placeholder for icon
80-
title: Text(app.name),
81-
subtitle: Text(app.iosLaunchScheme ?? "No ID"),
82-
),
83-
),
84-
);
85-
} catch (e) {
86-
// Debug print removed
87-
debugPrint('Error: $e');
88-
}
8948
},
9049
),
9150

9251
// Button to compose an email in the mail app
9352
ElevatedButton(
94-
child: const Text('Open mail app, with email already composed'),
53+
child: const Text('Compose Email'),
9554
onPressed: () async {
9655
OpenMailAppResult result;
9756

9857
try {
58+
// Compose a new email using the first available mail app
9959
result = await OpenMail.composeNewEmailInMailApp(
10060
nativePickerTitle: 'Select email app to compose',
10161
emailContent: emailContent);
10262

10363
if (!result.didOpen && !result.canOpen) {
10464
showNoMailAppsDialog(context);
105-
} else if (!result.didOpen && result.canOpen) {
106-
showDialog(
107-
context: context,
108-
builder: (_) => MailAppPickerDialog(
109-
mailApps: result.options,
110-
emailContent: emailContent,
111-
),
112-
);
11365
}
11466
} catch (e) {
11567
showDialog(
@@ -129,16 +81,75 @@ class MyApp extends StatelessWidget {
12981
},
13082
),
13183

132-
// Button to get the list of installed mail apps
84+
// Button to get the list of installed mail apps and open a specific one
13385
ElevatedButton(
134-
child: const Text("Get Mail Apps"),
86+
child: const Text("Open Specific One Mail App"),
13587
onPressed: () async {
13688
try {
137-
// Retrieve the list of installed mail apps
89+
// Open the first available mail app directly if only one is installed
13890
var apps = await OpenMail.getMailApps();
13991

92+
// If no mail apps are installed
93+
if (apps.isEmpty) {
94+
showNoMailAppsDialog(context);
95+
}
96+
// Show a dialog listing all available mail apps
97+
else {
98+
showDialog(
99+
context: context,
100+
builder: (context) {
101+
return AlertDialog(
102+
title: const Text('Installed Mail Apps'),
103+
content: SizedBox(
104+
width: double.maxFinite,
105+
child: ListView.builder(
106+
shrinkWrap: true,
107+
itemCount: apps.length,
108+
itemBuilder: (context, index) {
109+
final app = apps[index];
110+
return ListTile(
111+
onTap: () async {
112+
try {
113+
await OpenMail.openSpecificMailApp(
114+
app.name);
115+
} catch (e) {
116+
log('Error: $e');
117+
}
118+
},
119+
title: Text(app.name),
120+
subtitle: Text(Platform.isIOS
121+
? 'iOS Scheme: ${app.iosLaunchScheme}'
122+
: Platform.isAndroid
123+
? 'Package: ${app.nativeId}'
124+
: 'Unknown Platform'),
125+
);
126+
},
127+
),
128+
),
129+
actions: [
130+
TextButton(
131+
child: const Text('OK'),
132+
onPressed: () => Navigator.of(context).pop(),
133+
),
134+
],
135+
);
136+
},
137+
);
138+
}
139+
} catch (e) {
140140
// Debug print removed
141-
// Debug print removed
141+
}
142+
},
143+
),
144+
145+
// Get Mails Apps and Compose Email in Specific One
146+
147+
ElevatedButton(
148+
child: const Text("Compose Email in Specific Mail Apps"),
149+
onPressed: () async {
150+
try {
151+
// Retrieve the list of installed mail apps
152+
var apps = await OpenMail.getMailApps();
142153

143154
// If no mail apps are installed
144155
if (apps.isEmpty) {
@@ -149,18 +160,42 @@ class MyApp extends StatelessWidget {
149160
showDialog(
150161
context: context,
151162
builder: (context) {
152-
return MailAppPickerDialog(
153-
mailApps: apps,
154-
emailContent: EmailContent(
155-
to: ['[email protected]'], // Pre-filled recipient
156-
subject: 'Hello!', // Pre-filled subject
157-
body: 'How are you doing?', // Pre-filled body
158-
cc: [
159-
160-
161-
], // Pre-filled CC
162-
bcc: ['[email protected]'], // Pre-filled BCC
163+
return AlertDialog(
164+
title: const Text('Installed Mail Apps'),
165+
content: SizedBox(
166+
width: double.maxFinite,
167+
child: ListView.builder(
168+
shrinkWrap: true,
169+
itemCount: apps.length,
170+
itemBuilder: (context, index) {
171+
final app = apps[index];
172+
return ListTile(
173+
onTap: () async {
174+
try {
175+
await OpenMail
176+
.composeNewEmailInSpecificMailApp(
177+
mailApp: app,
178+
emailContent: emailContent);
179+
} catch (e) {
180+
log('Error: $e');
181+
}
182+
},
183+
title: Text(app.name),
184+
subtitle: Text(Platform.isIOS
185+
? 'iOS Scheme: ${app.iosLaunchScheme}'
186+
: Platform.isAndroid
187+
? 'Package: ${app.nativeId}'
188+
: 'Unknown Platform'),
189+
);
190+
},
191+
),
163192
),
193+
actions: [
194+
TextButton(
195+
child: const Text('OK'),
196+
onPressed: () => Navigator.of(context).pop(),
197+
),
198+
],
164199
);
165200
},
166201
);

0 commit comments

Comments
 (0)