diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index 5e278e8..8dba61c 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -49,6 +49,12 @@ "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "clock", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/clock-1.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, { "name": "code_builder", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/code_builder-4.11.1", @@ -79,6 +85,12 @@ "packageUri": "lib/", "languageVersion": "3.9" }, + { + "name": "fake_async", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/fake_async-1.3.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, { "name": "file", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/file-7.0.1", @@ -103,6 +115,18 @@ "packageUri": "lib/", "languageVersion": "3.5" }, + { + "name": "flutter_test", + "rootUri": "file:///opt/homebrew/Caskroom/flutter/2.10.4/flutter/packages/flutter_test", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "flutter_web_plugins", + "rootUri": "file:///opt/homebrew/Caskroom/flutter/2.10.4/flutter/packages/flutter_web_plugins", + "packageUri": "lib/", + "languageVersion": "3.8" + }, { "name": "glob", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/glob-2.1.3", @@ -115,6 +139,24 @@ "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "leak_tracker", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "leak_tracker_flutter_testing", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "leak_tracker_testing", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2", + "packageUri": "lib/", + "languageVersion": "3.2" + }, { "name": "lints", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/lints-5.1.1", @@ -123,9 +165,9 @@ }, { "name": "matcher", - "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/matcher-0.12.18", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/matcher-0.12.17", "packageUri": "lib/", - "languageVersion": "3.7" + "languageVersion": "3.4" }, { "name": "material_color_utilities", @@ -157,6 +199,12 @@ "packageUri": "lib/", "languageVersion": "3.9" }, + { + "name": "plugin_platform_interface", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "pub_semver", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/pub_semver-2.2.0", @@ -201,9 +249,9 @@ }, { "name": "test_api", - "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/test_api-0.7.9", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/test_api-0.7.7", "packageUri": "lib/", - "languageVersion": "3.7" + "languageVersion": "3.5" }, { "name": "typed_data", @@ -211,18 +259,78 @@ "packageUri": "lib/", "languageVersion": "3.5" }, + { + "name": "url_launcher", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher-6.3.2", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "url_launcher_android", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.28", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "url_launcher_ios", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.6", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "url_launcher_linux", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "url_launcher_macos", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.5", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "url_launcher_platform_interface", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "url_launcher_web", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "url_launcher_windows", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5", + "packageUri": "lib/", + "languageVersion": "3.8" + }, { "name": "vector_math", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/vector_math-2.2.0", "packageUri": "lib/", "languageVersion": "3.1" }, + { + "name": "vm_service", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/vm_service-15.0.2", + "packageUri": "lib/", + "languageVersion": "3.5" + }, { "name": "watcher", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/watcher-1.2.1", "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "web", + "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/web-1.1.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, { "name": "yaml", "rootUri": "file:///Users/rodydavis/.pub-cache/hosted/pub.dev/yaml-3.1.3", @@ -239,7 +347,13 @@ "name": "flutter_vibrate", "rootUri": "../packages/flutter_vibrate", "packageUri": "lib/", - "languageVersion": "3.10" + "languageVersion": "3.5" + }, + { + "name": "flutter_sms", + "rootUri": "../packages/flutter_sms", + "packageUri": "lib/", + "languageVersion": "3.5" } ], "generator": "pub", diff --git a/.dart_tool/package_graph.json b/.dart_tool/package_graph.json index bfa363c..cc379a1 100644 --- a/.dart_tool/package_graph.json +++ b/.dart_tool/package_graph.json @@ -1,5 +1,6 @@ { "roots": [ + "flutter_sms", "flutter_vibrate", "packages_dart_rodydavis" ], @@ -21,6 +22,20 @@ "pigeon" ] }, + { + "name": "flutter_sms", + "version": "3.0.0", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "url_launcher", + "web" + ], + "devDependencies": [ + "flutter_test", + "pigeon" + ] + }, { "name": "pigeon", "version": "26.1.5", @@ -38,10 +53,47 @@ ] }, { - "name": "flutter_lints", - "version": "5.0.0", + "name": "flutter_test", + "version": "0.0.0", "dependencies": [ - "lints" + "clock", + "collection", + "fake_async", + "flutter", + "leak_tracker_flutter_testing", + "matcher", + "meta", + "path", + "stack_trace", + "stream_channel", + "test_api", + "vector_math" + ] + }, + { + "name": "web", + "version": "1.1.1", + "dependencies": [] + }, + { + "name": "url_launcher", + "version": "6.3.2", + "dependencies": [ + "flutter", + "url_launcher_android", + "url_launcher_ios", + "url_launcher_linux", + "url_launcher_macos", + "url_launcher_platform_interface", + "url_launcher_web", + "url_launcher_windows" + ] + }, + { + "name": "flutter_web_plugins", + "version": "0.0.0", + "dependencies": [ + "flutter" ] }, { @@ -56,6 +108,13 @@ "vector_math" ] }, + { + "name": "flutter_lints", + "version": "5.0.0", + "dependencies": [ + "lints" + ] + }, { "name": "yaml", "version": "3.1.3", @@ -143,20 +202,137 @@ ] }, { - "name": "lints", - "version": "5.1.1", - "dependencies": [] + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" + ] }, { - "name": "sky_engine", - "version": "0.0.0", - "dependencies": [] + "name": "leak_tracker_flutter_testing", + "version": "3.0.10", + "dependencies": [ + "flutter", + "leak_tracker", + "leak_tracker_testing", + "matcher", + "meta" + ] }, { "name": "vector_math", "version": "2.2.0", "dependencies": [] }, + { + "name": "stack_trace", + "version": "1.12.1", + "dependencies": [ + "path" + ] + }, + { + "name": "clock", + "version": "1.1.2", + "dependencies": [] + }, + { + "name": "fake_async", + "version": "1.3.3", + "dependencies": [ + "clock", + "collection" + ] + }, + { + "name": "matcher", + "version": "0.12.17", + "dependencies": [ + "async", + "meta", + "stack_trace", + "term_glyph", + "test_api" + ] + }, + { + "name": "test_api", + "version": "0.7.7", + "dependencies": [ + "async", + "boolean_selector", + "collection", + "meta", + "source_span", + "stack_trace", + "stream_channel", + "string_scanner", + "term_glyph" + ] + }, + { + "name": "url_launcher_windows", + "version": "3.1.5", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_web", + "version": "2.4.1", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "url_launcher_platform_interface", + "web" + ] + }, + { + "name": "url_launcher_platform_interface", + "version": "2.3.2", + "dependencies": [ + "flutter", + "plugin_platform_interface" + ] + }, + { + "name": "url_launcher_macos", + "version": "3.2.5", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_linux", + "version": "3.2.2", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_ios", + "version": "6.3.6", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_android", + "version": "6.3.28", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "sky_engine", + "version": "0.0.0", + "dependencies": [] + }, { "name": "material_color_utilities", "version": "0.11.1", @@ -169,6 +345,11 @@ "version": "1.4.0", "dependencies": [] }, + { + "name": "lints", + "version": "5.1.1", + "dependencies": [] + }, { "name": "string_scanner", "version": "1.4.1", @@ -192,17 +373,6 @@ "path" ] }, - { - "name": "matcher", - "version": "0.12.18", - "dependencies": [ - "async", - "meta", - "stack_trace", - "term_glyph", - "test_api" - ] - }, { "name": "built_value", "version": "8.12.2", @@ -258,38 +428,51 @@ "meta" ] }, + { + "name": "async", + "version": "2.13.0", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "leak_tracker_testing", + "version": "3.0.2", + "dependencies": [ + "leak_tracker", + "matcher", + "meta" + ] + }, + { + "name": "leak_tracker", + "version": "11.0.2", + "dependencies": [ + "clock", + "collection", + "meta", + "path", + "vm_service" + ] + }, { "name": "term_glyph", "version": "1.2.2", "dependencies": [] }, { - "name": "test_api", - "version": "0.7.9", + "name": "boolean_selector", + "version": "2.1.2", "dependencies": [ - "async", - "boolean_selector", - "collection", - "meta", "source_span", - "stack_trace", - "stream_channel", - "string_scanner", - "term_glyph" + "string_scanner" ] }, { - "name": "stack_trace", - "version": "1.12.1", + "name": "plugin_platform_interface", + "version": "2.1.8", "dependencies": [ - "path" - ] - }, - { - "name": "async", - "version": "2.13.0", - "dependencies": [ - "collection", "meta" ] }, @@ -314,19 +497,9 @@ ] }, { - "name": "stream_channel", - "version": "2.1.4", - "dependencies": [ - "async" - ] - }, - { - "name": "boolean_selector", - "version": "2.1.2", - "dependencies": [ - "source_span", - "string_scanner" - ] + "name": "vm_service", + "version": "15.0.2", + "dependencies": [] } ], "configVersion": 1 diff --git a/packages/flutter_sms/.github/workflows/main.yml b/packages/flutter_sms/.github/workflows/main.yml deleted file mode 100644 index ae9ba6c..0000000 --- a/packages/flutter_sms/.github/workflows/main.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: github pages - -on: - push: - branches: - - master - -jobs: - deploy: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - - name: Setup Flutter - uses: subosito/flutter-action@v1 - with: - channel: 'dev' - - - name: Install - run: | - flutter config --enable-web - flutter pub get - - name: Build - run: cd example && flutter build web - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} - publish_dir: ./example/build/web diff --git a/packages/flutter_sms/CHANGELOG.md b/packages/flutter_sms/CHANGELOG.md index 5b89349..11b3233 100644 --- a/packages/flutter_sms/CHANGELOG.md +++ b/packages/flutter_sms/CHANGELOG.md @@ -1,3 +1,12 @@ +# 3.0.0 + +* **Breaking Change**: Migrated to Pigeon for type-safe platform channels. +* **Breaking Change**: Removed `plugin_platform_interface` dependency. +* **Breaking Change**: Removed `sendDirect` functionality and `SEND_SMS` permission requirement. +* Updated Dart SDK constraint to `^3.0.0`. +* Refactored package to use conditional imports for Web and Native support. +* Modernized example app with Material 3. + # 2.3.3 * Adding ability to send SMS directly on Android (#57). diff --git a/packages/flutter_sms/README.md b/packages/flutter_sms/README.md index 48da449..9cc9b08 100644 --- a/packages/flutter_sms/README.md +++ b/packages/flutter_sms/README.md @@ -1,95 +1,73 @@ -[![Flutter Community: flutter_sms](https://fluttercommunity.dev/_github/header/flutter_sms)](https://github.com/fluttercommunity/community) - -[![Buy Me A Coffee](https://img.shields.io/badge/Donate-Buy%20Me%20A%20Coffee-yellow.svg)](https://www.buymeacoffee.com/rodydavis) -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WSH3GVC49GNNJ) -![github pages](https://github.com/fluttercommunity/flutter_sms/workflows/github%20pages/badge.svg) -[![GitHub stars](https://img.shields.io/github/stars/fluttercommunity/flutter_sms?color=blue)](https://github.com/fluttercommunity/flutter_sms) -[![flutter_sms](https://img.shields.io/pub/v/flutter_sms.svg)](https://pub.dev/packages/flutter_sms) # flutter_sms -![alt-text-1](https://github.com/fluttercommunity/flutter_sms/blob/master/screenshots/ios_blank.PNG) +[![pub package](https://img.shields.io/pub/v/flutter_sms.svg)](https://pub.dev/packages/flutter_sms) -Online Demo: https://fluttercommunity.github.io/flutter_sms/ +Flutter Plugin for sending SMS and MMS on Android and iOS. If you send to more than one person, it will send as MMS. On iOS, if the number is an iPhone and iMessage is enabled, it will send as an iMessage. -## Description +## Features -Flutter Plugin for sending SMS and MMS on Android and iOS. If you send to more than one person it will send as MMS. On the iOS if the number is an iPhone and iMessage is enabled it will send as an iMessage. +- Send SMS/MMS to one or multiple recipients. +- Check if the device is capable of sending SMS. -## How To Use +- Catch errors when sending fails. -You can send multiple ways: +## Usage -1. Message and No People -2. People and No Message -3. Message and People +### Install -This will populate the correct fields. +Add `flutter_sms` as a dependency in your `pubspec.yaml` file. +```yaml +dependencies: + flutter_sms: ^3.0.0 +``` -## Example +### Import -Make sure to Install and Import the Package. - -``` dart +```dart import 'package:flutter_sms/flutter_sms.dart'; ``` -Create a function for sending messages. +### Example -``` dart -void _sendSMS(String message, List recipents) async { - String _result = await sendSMS(message: message, recipients: recipents) - .catchError((onError) { - print(onError); - }); -print(_result); +```dart +void _sendSMS() async { + List recipients = ["1234567890", "5556787676"]; + String message = "This is a test message!"; + + try { + String result = await sendSMS(message: message, recipients: recipients); + print(result); + } catch (error) { + print(error); + } } ``` -You can quickly send the message with this function. +### Check Capability -``` dart -String message = "This is a test message!"; -List recipents = ["1234567890", "5556787676"]; +You can check if the current device is capable of sending SMS. -_sendSMS(message, recipents); +```dart +bool canSend = await canSendSMS(); +if (!canSend) { + print("Device cannot send SMS."); +} ``` +### Launch SMS URL -## Sending Direct +Launch the SMS URL scheme directly. -**WARNING, there is a narrow category of apps that can get into the play store -using this feature. Using it is only advisable if you fit into this category or -you intent to distribute through a third party platform** - -On Android, you can skip the additional dialog with the sendDirect parameter. - -``` dart -String message = "This is a test message!"; -List recipents = ["1234567890", "5556787676"]; - - String _result = await sendSMS(message: message, recipients: recipents, sendDirect: true) - .catchError((onError) { - print(onError); - }); -print(_result); +```dart +await launchSms(message: "This is a test message!", number: "1234567890"); ``` -NOTE: This also requires the SEND_SMS permission to be added to the AndroidManifest.xml +### Launch SMS URL (Multiple Recipients) +Launch the SMS URL scheme with multiple recipients. + +```dart +await launchSmsMulti(message: "This is a test message!", numbers: ["1234567890", "5556787676"]); ``` - - - { - if (!canSendSMS()) { - result.error( - "device_not_capable", - "The current device is not capable of sending text messages.", - "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS.") - return - } - val message = call.argument("message") ?: "" - val recipients = call.argument("recipients") ?: "" - val sendDirect = call.argument("sendDirect") ?: false - sendSMS(result, recipients, message!!, sendDirect) - } - "canSendSMS" -> result.success(canSendSMS()) - else -> result.notImplemented() + + override fun sendSms(message: String, recipients: List, callback: (Result) -> Unit) { + if (!checkCanSendSms()) { + callback(Result.failure(FlutterError("device_not_capable", "The current device is not capable of sending text messages.", "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS."))) + return } + + val phones = recipients.joinToString(";") + callback(Result.success(sendSMSDialog(phones, message))) } @TargetApi(Build.VERSION_CODES.ECLAIR) - private fun canSendSMS(): Boolean { - if (!activity!!.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) - return false + override fun canSendSms(callback: (Result) -> Unit) { + callback(Result.success(checkCanSendSms())) + } + + @TargetApi(Build.VERSION_CODES.ECLAIR) + private fun checkCanSendSms(): Boolean { + if (activity == null) { + Log.d("FlutterSmsPlugin", "Activity is null") + return false + } + if (!activity!!.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + Log.d("FlutterSmsPlugin", "No TELEPHONY feature") + return false + } val intent = Intent(Intent.ACTION_SENDTO) - intent.data = Uri.parse("smsto:") - val activityInfo = intent.resolveActivityInfo(activity!!.packageManager, intent.flags.toInt()) - return !(activityInfo == null || !activityInfo.exported) + intent.data = Uri.parse("smsto:123456") + val activityInfo = intent.resolveActivityInfo(activity!!.packageManager, 0) + if (activityInfo == null || !activityInfo.exported) { + Log.d("FlutterSmsPlugin", "No activity to handle smsto intent or not exported") + return false + } + return true } - private fun sendSMS(result: Result, phones: String, message: String, sendDirect: Boolean) { - if (sendDirect) { - sendSMSDirect(result, phones, message); - } - else { - sendSMSDialog(result, phones, message); - } - } - - private fun sendSMSDirect(result: Result, phones: String, message: String) { - // SmsManager is android.telephony - val sentIntent = PendingIntent.getBroadcast(activity, 0, Intent("SMS_SENT_ACTION"), PendingIntent.FLAG_IMMUTABLE) - val mSmsManager = SmsManager.getDefault() - val numbers = phones.split(";") - - for (num in numbers) { - Log.d("Flutter SMS", "msg.length() : " + message.toByteArray().size) - if (message.toByteArray().size > 80) { - val partMessage = mSmsManager.divideMessage(message) - mSmsManager.sendMultipartTextMessage(num, null, partMessage, null, null) - } else { - mSmsManager.sendTextMessage(num, null, message, sentIntent, null) - } - } - - result.success("SMS Sent!") - } - - private fun sendSMSDialog(result: Result, phones: String, message: String) { + private fun sendSMSDialog(phones: String, message: String): String { val intent = Intent(Intent.ACTION_SENDTO) intent.data = Uri.parse("smsto:$phones") intent.putExtra("sms_body", message) intent.putExtra(Intent.EXTRA_TEXT, message) activity?.startActivityForResult(intent, REQUEST_CODE_SEND_SMS) - result.success("SMS Sent!") + return "SMS Sent!" } } + diff --git a/packages/flutter_sms/android/src/main/kotlin/com/example/flutter_sms/Messages.g.kt b/packages/flutter_sms/android/src/main/kotlin/com/example/flutter_sms/Messages.g.kt new file mode 100644 index 0000000..060a5ca --- /dev/null +++ b/packages/flutter_sms/android/src/main/kotlin/com/example/flutter_sms/Messages.g.kt @@ -0,0 +1,115 @@ +// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package com.example.flutter_sms + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object MessagesPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +private open class MessagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface SmsHostApi { + fun sendSms(message: String, recipients: List, callback: (Result) -> Unit) + fun canSendSms(callback: (Result) -> Unit) + + companion object { + /** The codec used by SmsHostApi. */ + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } + /** Sets up an instance of `SmsHostApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: SmsHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_sms.SmsHostApi.sendSms$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val messageArg = args[0] as String + val recipientsArg = args[1] as List + api.sendSms(messageArg, recipientsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_sms.SmsHostApi.canSendSms$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.canSendSms{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/flutter_sms/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_sms/example/android/app/src/main/AndroidManifest.xml index 8bffc78..19b1c36 100644 --- a/packages/flutter_sms/example/android/app/src/main/AndroidManifest.xml +++ b/packages/flutter_sms/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - + + + + + diff --git a/packages/flutter_sms/example/lib/main.dart b/packages/flutter_sms/example/lib/main.dart index edac5f7..a77839b 100644 --- a/packages/flutter_sms/example/lib/main.dart +++ b/packages/flutter_sms/example/lib/main.dart @@ -65,7 +65,8 @@ class SmsViewModel extends ChangeNotifier { _setLoading(true); try { _canSendSms = await canSendSMS(); - } catch (e) { + } catch (e, t) { + debugPrint('Error checking capability: $e\n$t'); _status = 'Error checking capability: $e'; } finally { _setLoading(false); @@ -90,7 +91,7 @@ class SmsViewModel extends ChangeNotifier { notifyListeners(); } - Future send({bool sendDirect = false}) async { + Future send() async { if (_recipients.isEmpty || _message.isEmpty) { _status = 'Please add at least one recipient and a message.'; notifyListeners(); @@ -105,10 +106,10 @@ class SmsViewModel extends ChangeNotifier { final result = await sendSMS( message: _message, recipients: _recipients, - sendDirect: sendDirect, ); _status = result; - } catch (e) { + } catch (e, t) { + debugPrint('Error sending SMS: $e\n$t'); _status = e.toString(); } finally { _setLoading(false); @@ -270,20 +271,6 @@ class _SmsHomePageState extends State { padding: const EdgeInsets.symmetric(vertical: 16), ), ), - if (Theme.of(context).platform == - TargetPlatform.android) ...[ - const SizedBox(height: 12), - OutlinedButton.icon( - onPressed: _viewModel.isLoading - ? null - : () => _viewModel.send(sendDirect: true), - icon: const Icon(Icons.flash_on), - label: const Text('Send Direct (Android)'), - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - ), - ), - ], ], ), ), diff --git a/packages/flutter_sms/ios/Classes/Messages.g.swift b/packages/flutter_sms/ios/Classes/Messages.g.swift new file mode 100644 index 0000000..3b121f7 --- /dev/null +++ b/packages/flutter_sms/ios/Classes/Messages.g.swift @@ -0,0 +1,135 @@ +// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + + +private class MessagesPigeonCodecReader: FlutterStandardReader { +} + +private class MessagesPigeonCodecWriter: FlutterStandardWriter { +} + +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return MessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return MessagesPigeonCodecWriter(data: data) + } +} + +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol SmsHostApi { + func sendSms(message: String, recipients: [String], completion: @escaping (Result) -> Void) + func canSendSms(completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class SmsHostApiSetup { + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } + /// Sets up an instance of `SmsHostApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: SmsHostApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let sendSmsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.flutter_sms.SmsHostApi.sendSms\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + sendSmsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let messageArg = args[0] as! String + let recipientsArg = args[1] as! [String] + api.sendSms(message: messageArg, recipients: recipientsArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + sendSmsChannel.setMessageHandler(nil) + } + let canSendSmsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.flutter_sms.SmsHostApi.canSendSms\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + canSendSmsChannel.setMessageHandler { _, reply in + api.canSendSms { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + canSendSmsChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/flutter_sms/ios/Classes/SwiftFlutterSmsPlugin.swift b/packages/flutter_sms/ios/Classes/SwiftFlutterSmsPlugin.swift index 70cbc8a..92a5a7a 100644 --- a/packages/flutter_sms/ios/Classes/SwiftFlutterSmsPlugin.swift +++ b/packages/flutter_sms/ios/Classes/SwiftFlutterSmsPlugin.swift @@ -2,60 +2,50 @@ import Flutter import UIKit import MessageUI -public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, UINavigationControllerDelegate, MFMessageComposeViewControllerDelegate { - var result: FlutterResult? +public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, SmsHostApi, UINavigationControllerDelegate, MFMessageComposeViewControllerDelegate { + var result: ((Result) -> Void)? var _arguments = [String: Any]() public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "flutter_sms", binaryMessenger: registrar.messenger()) let instance = SwiftFlutterSmsPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) + SmsHostApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance) } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "sendSMS": - _arguments = call.arguments as! [String : Any]; - #if targetEnvironment(simulator) - result(FlutterError( - code: "message_not_sent", - message: "Cannot send message on this device!", - details: "Cannot send SMS and MMS on a Simulator. Test on a real device." - ) - ) - #else - if (MFMessageComposeViewController.canSendText()) { - self.result = result - let controller = MFMessageComposeViewController() - controller.body = _arguments["message"] as? String - controller.recipients = _arguments["recipients"] as? [String] - controller.messageComposeDelegate = self - UIApplication.shared.keyWindow?.rootViewController?.present(controller, animated: true, completion: nil) - } else { - result(FlutterError( - code: "device_not_capable", - message: "The current device is not capable of sending text messages.", - details: "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS." - ) - ) - } - #endif + public func sendSms(message: String, recipients: [String], completion: @escaping (Result) -> Void) { + #if targetEnvironment(simulator) + completion(.failure(FlutterError( + code: "message_not_sent", + message: "Cannot send message on this device!", + details: "Cannot send SMS and MMS on a Simulator. Test on a real device." + ))) + #else + if (MFMessageComposeViewController.canSendText()) { + self.result = completion + let controller = MFMessageComposeViewController() + controller.body = message + controller.recipients = recipients + controller.messageComposeDelegate = self + UIApplication.shared.keyWindow?.rootViewController?.present(controller, animated: true, completion: nil) + } else { + completion(.failure(FlutterError( + code: "device_not_capable", + message: "The current device is not capable of sending text messages.", + details: "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS." + ))) + } + #endif + } - case "canSendSMS": - #if targetEnvironment(simulator) - result(false) - #else - if (MFMessageComposeViewController.canSendText()) { - result(true) - } else { - result(false) - } - #endif - - default: - result(FlutterMethodNotImplemented) - break - } + public func canSendSms() -> Bool { + #if targetEnvironment(simulator) + return false + #else + if (MFMessageComposeViewController.canSendText()) { + return true + } else { + return false + } + #endif } public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { @@ -65,7 +55,7 @@ public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, UINavigationControl MessageComposeResult.failed: "failed", ] if let callback = self.result { - callback(map[result]) + callback(.success(map[result] ?? "unknown")) } UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil) } diff --git a/packages/flutter_sms/ios/Flutter/ephemeral/flutter_lldb_helper.py b/packages/flutter_sms/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/packages/flutter_sms/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/packages/flutter_sms/ios/Flutter/ephemeral/flutter_lldbinit b/packages/flutter_sms/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/packages/flutter_sms/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/packages/flutter_sms/lib/flutter_sms.dart b/packages/flutter_sms/lib/flutter_sms.dart index b6a61e5..a50c2b2 100644 --- a/packages/flutter_sms/lib/flutter_sms.dart +++ b/packages/flutter_sms/lib/flutter_sms.dart @@ -1,32 +1,38 @@ import 'dart:async'; -import 'src/flutter_sms_platform.dart'; +import 'src/flutter_sms_io.dart' + if (dart.library.js_interop) 'src/flutter_sms_web.dart'; + +final FlutterSms _flutterSms = FlutterSms(); /// Open SMS Dialog on iOS/Android/Web Future sendSMS({ required String message, required List recipients, - bool sendDirect = false, -}) => - FlutterSmsPlatform.instance.sendSMS( - message: message, - recipients: recipients, - sendDirect: sendDirect, - ); +}) { + return _flutterSms.sendSMS( + message: message, + recipients: recipients, + ); +} /// Launch SMS Url Scheme on all platforms Future launchSms({ String? message, String? number, -}) => - FlutterSmsPlatform.instance.launchSms(number, message); +}) { + return _flutterSms.launchSms(number, message); +} /// Launch SMS Url Scheme on all platforms Future launchSmsMulti({ required String message, required List numbers, -}) => - FlutterSmsPlatform.instance.launchSmsMulti(numbers, message); +}) { + return _flutterSms.launchSmsMulti(numbers, message); +} /// Check if you can send SMS on this platform -Future canSendSMS() => FlutterSmsPlatform.instance.canSendSMS(); +Future canSendSMS() { + return _flutterSms.canSendSMS(); +} diff --git a/packages/flutter_sms/lib/flutter_sms_web.dart b/packages/flutter_sms/lib/flutter_sms_web.dart deleted file mode 100644 index b57c4b4..0000000 --- a/packages/flutter_sms/lib/flutter_sms_web.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -import 'src/flutter_sms_platform.dart'; - -class FlutterSmsPlugin extends FlutterSmsPlatform { - static void registerWith(Registrar registrar) { - // WidgetsFlutterBinding.ensureInitialized(); - FlutterSmsPlatform.instance = FlutterSmsPlugin(); - } - - @override - Future sendSMS({ - required String message, - required List recipients, - bool sendDirect = false, - }) async { - bool _messageSent = - await FlutterSmsPlatform.instance.launchSmsMulti(recipients, message); - if (_messageSent) return 'Message Sent!'; - return 'Error Sending Message!'; - } - - @override - Future canSendSMS() => Future.value(true); -} diff --git a/packages/flutter_sms/lib/src/flutter_sms_interface.dart b/packages/flutter_sms/lib/src/flutter_sms_interface.dart new file mode 100644 index 0000000..3f8ace4 --- /dev/null +++ b/packages/flutter_sms/lib/src/flutter_sms_interface.dart @@ -0,0 +1,14 @@ +abstract class FlutterSmsPlatform { + const FlutterSmsPlatform(); + + Future sendSMS({ + required String message, + required List recipients, + }); + + Future canSendSMS(); + + Future launchSmsMulti(List numbers, [String? body]); + + Future launchSms(String? number, [String? body]); +} diff --git a/packages/flutter_sms/lib/src/flutter_sms_io.dart b/packages/flutter_sms/lib/src/flutter_sms_io.dart new file mode 100644 index 0000000..b03e65c --- /dev/null +++ b/packages/flutter_sms/lib/src/flutter_sms_io.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:flutter_sms/src/messages.g.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'flutter_sms_interface.dart'; +import 'user_agent/io.dart'; + +class FlutterSms extends FlutterSmsPlatform { + final SmsHostApi _api = SmsHostApi(); + + /// + /// + Future sendSMS({ + required String message, + required List recipients, + }) { + return _api.sendSms(message, recipients); + } + + Future canSendSMS() { + return _api.canSendSms(); + } + + Future launchSmsMulti(List numbers, [String? body]) { + if (numbers.length == 1) { + return launchSms(numbers.first, body); + } + String _phones = numbers.join(';'); + if (body != null) { + final _body = Uri.encodeComponent(body); + return launchUrl( + Uri.parse('sms:/open?addresses=$_phones${separator}body=$_body')); + } + return launchUrl(Uri.parse('sms:/open?addresses=$_phones')); + } + + Future launchSms(String? number, [String? body]) { + // ignore: parameter_assignments + number ??= ''; + if (body != null) { + final _body = Uri.encodeComponent(body); + return launchUrl(Uri.parse('sms:/$number${separator}body=$_body')); + } + return launchUrl(Uri.parse('sms:/$number')); + } + + String get separator => isCupertino() ? '&' : '?'; +} diff --git a/packages/flutter_sms/lib/src/flutter_sms_platform.dart b/packages/flutter_sms/lib/src/flutter_sms_platform.dart deleted file mode 100644 index cef5396..0000000 --- a/packages/flutter_sms/lib/src/flutter_sms_platform.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'dart:async'; -import 'dart:io' show Platform; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'user_agent/io.dart' if (dart.library.html) 'user_agent/web.dart'; - -const MethodChannel _channel = MethodChannel('flutter_sms'); - -class FlutterSmsPlatform extends PlatformInterface { - /// Constructs a FlutterSmsPlatform. - FlutterSmsPlatform() : super(token: _token); - - static final Object _token = Object(); - - static FlutterSmsPlatform _instance = FlutterSmsPlatform(); - - /// The default instance of [FlutterSmsPlatform] to use. - /// - /// Defaults to [MethodChannelFlutterSmsPlatform]. - static FlutterSmsPlatform get instance => _instance; - - /// Platform-specific plugins should set this with their own platform-specific - /// class that extends [FlutterSmsPlatform] when they register themselves. - // TODO(amirh): Extract common platform interface logic. - // https://github.com/flutter/flutter/issues/43368 - static set instance(FlutterSmsPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - /// - /// - Future sendSMS({ - required String message, - required List recipients, - bool sendDirect = false, - }) { - final mapData = {}; - mapData['message'] = message; - if (!kIsWeb && Platform.isIOS) { - mapData['recipients'] = recipients; - return _channel - .invokeMethod('sendSMS', mapData) - .then((value) => value ?? 'Error sending sms'); - } else { - String _phones = recipients.join(';'); - mapData['recipients'] = _phones; - mapData['sendDirect'] = sendDirect; - return _channel - .invokeMethod('sendSMS', mapData) - .then((value) => value ?? 'Error sending sms'); - } - } - - Future canSendSMS() { - return _channel - .invokeMethod('canSendSMS') - .then((value) => value ?? false); - } - - Future launchSmsMulti(List numbers, [String? body]) { - if (numbers.length == 1) { - return launchSms(numbers.first, body); - } - String _phones = numbers.join(';'); - if (body != null) { - final _body = Uri.encodeComponent(body); - return launchUrl( - Uri.parse('sms:/open?addresses=$_phones${separator}body=$_body')); - } - return launchUrl(Uri.parse('sms:/open?addresses=$_phones')); - } - - Future launchSms(String? number, [String? body]) { - // ignore: parameter_assignments - number ??= ''; - if (body != null) { - final _body = Uri.encodeComponent(body); - return launchUrl(Uri.parse('sms:/$number${separator}body=$_body')); - } - return launchUrl(Uri.parse('sms:/$number')); - } - - String get separator => isCupertino() ? '&' : '?'; -} diff --git a/packages/flutter_sms/lib/src/flutter_sms_web.dart b/packages/flutter_sms/lib/src/flutter_sms_web.dart new file mode 100644 index 0000000..9c17d66 --- /dev/null +++ b/packages/flutter_sms/lib/src/flutter_sms_web.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:url_launcher/url_launcher.dart'; + +import 'flutter_sms_interface.dart'; +import 'user_agent/web.dart'; + +class FlutterSms extends FlutterSmsPlatform { + @override + Future sendSMS({ + required String message, + required List recipients, + }) async { + bool messageSent = await launchSmsMulti(recipients, message); + if (messageSent) return 'Message Sent!'; + return 'Error Sending Message!'; + } + + @override + Future canSendSMS() => Future.value(true); + + @override + Future launchSmsMulti(List numbers, [String? body]) { + if (numbers.length == 1) { + return launchSms(numbers.first, body); + } + String phones = numbers.join(';'); + if (body != null) { + final encodedBody = Uri.encodeComponent(body); + return launchUrl( + Uri.parse('sms:/open?addresses=$phones$separator}body=$encodedBody')); + } + return launchUrl(Uri.parse('sms:/open?addresses=$phones')); + } + + @override + Future launchSms(String? number, [String? body]) { + // ignore: parameter_assignments + number ??= ''; + if (body != null) { + final encodedBody = Uri.encodeComponent(body); + return launchUrl(Uri.parse('sms:/$number$separator}body=$encodedBody')); + } + return launchUrl(Uri.parse('sms:/$number')); + } + + String get separator => isCupertino() ? '&' : '?'; +} diff --git a/packages/flutter_sms/lib/src/messages.g.dart b/packages/flutter_sms/lib/src/messages.g.dart new file mode 100644 index 0000000..222738f --- /dev/null +++ b/packages/flutter_sms/lib/src/messages.g.dart @@ -0,0 +1,106 @@ +// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class SmsHostApi { + /// Constructor for [SmsHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + SmsHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future sendSms(String message, List recipients) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_sms.SmsHostApi.sendSms$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([message, recipients]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + Future canSendSms() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_sms.SmsHostApi.canSendSms$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } +} diff --git a/packages/flutter_sms/lib/src/user_agent/web.dart b/packages/flutter_sms/lib/src/user_agent/web.dart index 9caeec0..3d78653 100644 --- a/packages/flutter_sms/lib/src/user_agent/web.dart +++ b/packages/flutter_sms/lib/src/user_agent/web.dart @@ -16,5 +16,11 @@ bool isCupertino() { return true; } } + final userAgent = web.window.navigator.userAgent.toLowerCase(); + if (userAgent.contains('iphone') || + userAgent.contains('ipad') || + userAgent.contains('ipod')) { + return true; + } return false; } diff --git a/packages/flutter_sms/pigeons/messages.dart b/packages/flutter_sms/pigeons/messages.dart new file mode 100644 index 0000000..502205d --- /dev/null +++ b/packages/flutter_sms/pigeons/messages.dart @@ -0,0 +1,16 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + kotlinOut: 'android/src/main/kotlin/com/example/flutter_sms/Messages.g.kt', + kotlinOptions: KotlinOptions(package: 'com.example.flutter_sms'), + swiftOut: 'ios/Classes/Messages.g.swift', +)) +@HostApi() +abstract class SmsHostApi { + @async + String sendSms(String message, List recipients); + + @async + bool canSendSms(); +} diff --git a/packages/flutter_sms/pubspec.yaml b/packages/flutter_sms/pubspec.yaml index 39bb3cf..d49129e 100644 --- a/packages/flutter_sms/pubspec.yaml +++ b/packages/flutter_sms/pubspec.yaml @@ -1,26 +1,27 @@ name: flutter_sms -description: A Flutter plugin to Send SMS and MMS on iOS and Android. If iMessage is enabled it will send as iMessage on iOS. This plugin must be tested on a real device on iOS. -version: 2.3.3 -homepage: https://github.com/rodydavis/plugins -repository: https://github.com/fluttercommunity/flutter_sms +description: A Flutter plugin to send SMS and MMS on iOS and Android. +version: 3.0.0 +homepage: https://github.com/rodydavis/packages.dart +repository: https://github.com/rodydavis/packages.dart maintainer: Rody Davis (@rodydavis) +resolution: workspace environment: - sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.5.0 + flutter: ^3.38.5 dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter - plugin_platform_interface: ^2.1.0 url_launcher: ^6.1.10 web: ^1.1.1 dev_dependencies: flutter_test: sdk: flutter + pigeon: ^26.1.5 flutter: plugin: @@ -29,7 +30,5 @@ flutter: package: com.example.flutter_sms pluginClass: FlutterSmsPlugin ios: - pluginClass: FlutterSmsPlugin - web: - pluginClass: FlutterSmsPlugin - fileName: flutter_sms_web.dart + pluginClass: SwiftFlutterSmsPlugin + diff --git a/packages/flutter_sms/screenshots/android_blank.png b/packages/flutter_sms/screenshots/android_blank.png deleted file mode 100644 index 7741708..0000000 Binary files a/packages/flutter_sms/screenshots/android_blank.png and /dev/null differ diff --git a/packages/flutter_sms/screenshots/android_mms.png b/packages/flutter_sms/screenshots/android_mms.png deleted file mode 100644 index 1f29b11..0000000 Binary files a/packages/flutter_sms/screenshots/android_mms.png and /dev/null differ diff --git a/packages/flutter_sms/screenshots/android_multiple.png b/packages/flutter_sms/screenshots/android_multiple.png deleted file mode 100644 index 13cba58..0000000 Binary files a/packages/flutter_sms/screenshots/android_multiple.png and /dev/null differ diff --git a/packages/flutter_sms/screenshots/android_single.png b/packages/flutter_sms/screenshots/android_single.png deleted file mode 100644 index 66af6a3..0000000 Binary files a/packages/flutter_sms/screenshots/android_single.png and /dev/null differ diff --git a/packages/flutter_sms/screenshots/android_sms.png b/packages/flutter_sms/screenshots/android_sms.png deleted file mode 100644 index e2133b4..0000000 Binary files a/packages/flutter_sms/screenshots/android_sms.png and /dev/null differ diff --git a/packages/flutter_sms/screenshots/ios_blank.PNG b/packages/flutter_sms/screenshots/ios_blank.PNG deleted file mode 100644 index aa5ba1c..0000000 Binary files a/packages/flutter_sms/screenshots/ios_blank.PNG and /dev/null differ diff --git a/packages/flutter_sms/screenshots/ios_mms.PNG b/packages/flutter_sms/screenshots/ios_mms.PNG deleted file mode 100644 index bf5833e..0000000 Binary files a/packages/flutter_sms/screenshots/ios_mms.PNG and /dev/null differ diff --git a/packages/flutter_sms/screenshots/ios_multiple.PNG b/packages/flutter_sms/screenshots/ios_multiple.PNG deleted file mode 100644 index 78057fb..0000000 Binary files a/packages/flutter_sms/screenshots/ios_multiple.PNG and /dev/null differ diff --git a/packages/flutter_sms/screenshots/ios_single.PNG b/packages/flutter_sms/screenshots/ios_single.PNG deleted file mode 100644 index 79184b1..0000000 Binary files a/packages/flutter_sms/screenshots/ios_single.PNG and /dev/null differ diff --git a/packages/flutter_sms/screenshots/ios_sms.PNG b/packages/flutter_sms/screenshots/ios_sms.PNG deleted file mode 100644 index 797834d..0000000 Binary files a/packages/flutter_sms/screenshots/ios_sms.PNG and /dev/null differ diff --git a/packages/flutter_vibrate/.github/workflows/main.yml b/packages/flutter_vibrate/.github/workflows/main.yml deleted file mode 100644 index 1c693cc..0000000 --- a/packages/flutter_vibrate/.github/workflows/main.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: github pages - -on: - push: - branches: - - master - -jobs: - deploy: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - - name: Setup Flutter - uses: subosito/flutter-action@v1 - with: - channel: 'dev' - - - name: Install - run: | - flutter config --enable-web - flutter pub get - - name: Build - run: cd example && flutter build web - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./example/build/web diff --git a/packages/flutter_vibrate/pubspec.yaml b/packages/flutter_vibrate/pubspec.yaml index 8bdd0fa..80ac508 100644 --- a/packages/flutter_vibrate/pubspec.yaml +++ b/packages/flutter_vibrate/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_vibrate description: A Haptic Feedback and Vibration plugin. version: 1.4.0 environment: - sdk: ^3.10.4 + sdk: ^3.5.0 flutter: ^3.38.5 maintainer: Rody Davis (@rodydavis) homepage: https://github.com/rodydavis/packages.dart diff --git a/pubspec.lock b/pubspec.lock index 9029c5a..9e0999b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" code_builder: dependency: transitive description: @@ -105,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" file: dependency: transitive description: @@ -134,6 +150,16 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -150,6 +176,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" lints: dependency: transitive description: @@ -162,10 +212,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -206,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "26.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" pub_semver: dependency: transitive description: @@ -263,10 +321,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.7" typed_data: dependency: transitive description: @@ -275,6 +333,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: @@ -283,6 +405,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" watcher: dependency: transitive description: @@ -291,6 +421,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" yaml: dependency: transitive description: @@ -300,5 +438,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.4 <4.0.0" - flutter: ">=3.38.5 <4.0.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.38.5" diff --git a/pubspec.yaml b/pubspec.yaml index 970b6d1..1b136f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,4 +6,4 @@ environment: workspace: - packages/flutter_vibrate - \ No newline at end of file + - packages/flutter_sms \ No newline at end of file