diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 6180784..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,44 +0,0 @@ -group 'com.example.flutter_sms' -version '1.0-SNAPSHOT' - -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.4' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - jcenter() - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdkVersion 31 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..557fb43 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") version "2.1.0" +} + +group = "com.example.flutter_sms" +version = "1.0-SNAPSHOT" + +android { + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + defaultConfig { + minSdk = flutter.minSdkVersion + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + sourceSets["main"].java.srcDirs("src/main/kotlin") + + lint { + disable.add("InvalidPackage") + } +} + +repositories { + google() + mavenCentral() // jcenter is deprecated; use mavenCentral +} + +dependencies { + implementation("androidx.core:core-ktx:1.16.0") + } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index d1c2b22..3ed573f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index c0b3294..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'flutter_sms' diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..8a0ff18 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "flutter_sms" diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 813bc01..c7e7078 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,2 @@ - - + + \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/flutter_sms/FlutterSmsPlugin.kt b/android/src/main/kotlin/com/example/flutter_sms/FlutterSmsPlugin.kt index 25af08c..6ed5952 100644 --- a/android/src/main/kotlin/com/example/flutter_sms/FlutterSmsPlugin.kt +++ b/android/src/main/kotlin/com/example/flutter_sms/FlutterSmsPlugin.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.telephony.SmsManager +import android.telephony.TelephonyManager import android.util.Log import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin @@ -18,7 +19,6 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { @@ -59,16 +59,6 @@ class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { mChannel.setMethodCallHandler(null) } - // V1 embedding entry point. This is deprecated and will be removed in a future Flutter - // release but we leave it here in case someone's app does not utilize the V2 embedding yet. - companion object { - @JvmStatic - fun registerWith(registrar: Registrar) { - val inst = FlutterSmsPlugin() - inst.activity = registrar.activity() - inst.setupCallbackChannels(registrar.messenger()) - } - } override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { @@ -92,12 +82,33 @@ class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { @TargetApi(Build.VERSION_CODES.ECLAIR) private fun canSendSMS(): Boolean { - if (!activity!!.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) - 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) + return try { + // Check if we can create an SMS intent + val intent = Intent(Intent.ACTION_SENDTO) + intent.data = Uri.parse("smsto:") + val activityInfo = intent.resolveActivityInfo(activity!!.packageManager, intent.flags.toInt()) + + // If we can resolve the SMS intent, we can send SMS + if (activityInfo != null && activityInfo.exported) { + return true + } + + // Alternative check: try to get SmsManager + try { + val smsManager = SmsManager.getDefault() + // If we can get SmsManager without exception, we can likely send SMS + return true + } catch (e: SecurityException) { + Log.w("FlutterSms", "SecurityException when checking SmsManager: ${e.message}") + return false + } catch (e: Exception) { + Log.w("FlutterSms", "Exception when checking SmsManager: ${e.message}") + return false + } + } catch (e: Exception) { + Log.e("FlutterSms", "Error checking SMS capability: ${e.message}") + false + } } private fun sendSMS(result: Result, phones: String, message: String, sendDirect: Boolean) { @@ -109,31 +120,111 @@ class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } } - 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) - } + // private fun sendSMSDirect(result: Result, phones: String, message: String) { + // try { + // // 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!") + // } catch (e: SecurityException) { + // result.error("permission_denied", "SMS permission denied: ${e.message}", null) + // } catch (e: Exception) { + // result.error("send_failed", "Failed to send SMS: ${e.message}", null) + // } + // } + +private fun sendSMSDirect(result: Result, phones: String, message: String) { + try { + // Create PendingIntent with FLAG_IMMUTABLE for Android 12+ compatibility + val sentIntent = PendingIntent.getBroadcast( + activity, + 0, + Intent("SMS_SENT_ACTION"), + PendingIntent.FLAG_IMMUTABLE + ) + + val smsManager = SmsManager.getDefault() + val numbers = phones.split(";") + + for (num in numbers) { + val trimmedNum = num.trim() + if (trimmedNum.isEmpty()) continue + + Log.d("Flutter SMS", "Message length (bytes): ${message.toByteArray().size}") + + // Use more accurate SMS length calculation (160 chars for GSM 7-bit) + if (message.length > 160 || message.toByteArray().size > 160) { + val partMessages = smsManager.divideMessage(message) + + // Create sent and delivery intents for multipart messages + val sentIntents = arrayListOf() + val deliveryIntents = arrayListOf() + + repeat(partMessages.size) { index -> + sentIntents.add( + PendingIntent.getBroadcast( + activity, + index, + Intent("SMS_SENT_ACTION"), + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + } + + smsManager.sendMultipartTextMessage( + trimmedNum, + null, + partMessages, + sentIntents, + deliveryIntents.takeIf { it.isNotEmpty() } + ) + } else { + smsManager.sendTextMessage( + trimmedNum, + null, + message, + sentIntent, + null + ) + } + } + + result.success("SMS Sent!") + + } catch (e: SecurityException) { + Log.e("Flutter SMS", "Security exception: ${e.message}") + result.error("permission_denied", "SMS permission denied: ${e.message}", null) + } catch (e: IllegalArgumentException) { + Log.e("Flutter SMS", "Invalid argument: ${e.message}") + result.error("invalid_argument", "Invalid SMS parameters: ${e.message}", null) + } catch (e: Exception) { + Log.e("Flutter SMS", "SMS send failed: ${e.message}") + result.error("send_failed", "Failed to send SMS: ${e.message}", null) } - - result.success("SMS Sent!") - } +} private fun sendSMSDialog(result: Result, phones: String, message: 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!") + try { + 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!") + } catch (e: Exception) { + result.error("intent_failed", "Failed to open SMS app: ${e.message}", null) + } } -} +} \ No newline at end of file diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index 1f87844..2b5c1d9 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_sms","path":"/Users/dsilk/Development/flutter/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_ios","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_ios-6.0.15/","dependencies":[]}],"android":[{"name":"flutter_sms","path":"/Users/dsilk/Development/flutter/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_android","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_android-6.0.15/","dependencies":[]}],"macos":[{"name":"url_launcher_macos","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-3.0.0/","dependencies":[]}],"linux":[{"name":"url_launcher_linux","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-3.0.0/","dependencies":[]}],"windows":[{"name":"url_launcher_windows","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-3.0.0/","dependencies":[]}],"web":[{"name":"flutter_sms","path":"/Users/dsilk/Development/flutter/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_web","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-2.0.8/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_sms","dependencies":["url_launcher"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2022-02-14 16:50:47.982162","version":"2.10.1"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_sms","path":"/Users/hafeezrana/Documents/flutter_projects/Open Source Plugins/flutter_sms/","native_build":true,"dependencies":[],"dev_dependency":true},{"name":"permission_handler_apple","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_sms","path":"/Users/hafeezrana/Documents/flutter_projects/Open Source Plugins/flutter_sms/","native_build":true,"dependencies":[],"dev_dependency":true},{"name":"permission_handler_android","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/permission_handler_android-13.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.16/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"url_launcher_macos","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"url_launcher_linux","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"permission_handler_windows","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/permission_handler_windows-0.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_sms","path":"/Users/hafeezrana/Documents/flutter_projects/Open Source Plugins/flutter_sms/","dependencies":[],"dev_dependency":true},{"name":"permission_handler_html","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/permission_handler_html-0.1.3+5/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/hafeezrana/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_sms","dependencies":["url_launcher"]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_html","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_html","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2025-06-01 22:32:34.372745","version":"3.32.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/example/.metadata b/example/.metadata index 71d86fd..0d9855e 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,27 @@ # This file should be version controlled and should not be manually edited. version: - revision: a817b4e6a4e80deefb88373d2a548b97c90d19c4 - channel: master + revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + - platform: ios + create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore index bc2100d..be3943c 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -5,3 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index f95d0d7..0000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 31 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion 16 - targetSdkVersion 31 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 0000000..630a6fc --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.flutter_sms_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.flutter_sms_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 26 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index c208884..399f698 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 6fb01f8..2af3259 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,45 +1,30 @@ - - + + - - - - + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> - - + + + + + + + + + \ No newline at end of file diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt deleted file mode 100644 index e793a00..0000000 --- a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/example/android/app/src/main/kotlin/com/example/flutter_sms_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/flutter_sms_example/MainActivity.kt new file mode 100644 index 0000000..5887331 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/flutter_sms_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.flutter_sms_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 1f83a33..cb1ef88 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index c208884..399f698 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index 79f6709..0000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.4' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 0000000..ce427d9 --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,68 @@ +allprojects { + repositories { + google() + mavenCentral() + } + + // Force JVM target for all projects after evaluation + afterEvaluate { + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "17" + } + } + + tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + } + + // Force Android plugin configurations if present + if (plugins.hasPlugin("com.android.application") || plugins.hasPlugin("com.android.library")) { + extensions.configure { + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + } + + // Force Kotlin options for Android + if (plugins.hasPlugin("kotlin-android")) { + extensions.configure { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } + } + } + } + } +} + +// Original Kotlin and Java compile task configurations (keep as backup) +allprojects { + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "17" + } + } + tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} + +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} \ No newline at end of file diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 38c8d45..f018a61 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 493072b..ac3b479 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index 5a2f14f..0000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -include ':app' - -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} - -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef60..7a7f987 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f7..7c56964 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba1..ec97fc6 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e934..c4855bf 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile index 6697f0a..6649374 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -10,78 +10,34 @@ project 'Runner', { 'Release' => :release, } -def parse_KV_file(file, separator='=') - file_abs_path = File.expand_path(file) - if !File.exists? file_abs_path - return []; +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end - generated_key_values = {} - skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) do |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - generated_key_values[podname] = podpath - else - puts "Invalid plugin specification: #{line}" - end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches end - generated_key_values + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end -target 'Runner' do - use_frameworks! - use_modular_headers! - - # Flutter Pod - - copied_flutter_dir = File.join(__dir__, 'Flutter') - copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') - copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') - unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) - # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. - # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. - # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') - unless File.exist?(generated_xcode_build_settings_path) - raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) - cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; +flutter_ios_podfile_setup - unless File.exist?(copied_framework_path) - FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) - end - unless File.exist?(copied_podspec_path) - FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) - end - end - - # Keep pod path relative so it can be checked into Podfile.lock. - pod 'Flutter', :path => 'Flutter' - - # Plugin Pods +target 'Runner' do + use_frameworks! - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') - plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.each do |name, path| - symlink = File.join('.symlinks', 'plugins', name) - File.symlink(path, symlink) - pod name, :path => File.join(symlink, 'ios') + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['ENABLE_BITCODE'] = 'NO' - end + flutter_additional_ios_build_settings(target) end end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 5f1bf4c..62c0680 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,31 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1E3DBFDC423C4C6577F4360A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F4AD80A94A4424BDAC710E6 /* Pods_RunnerTests.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 56E6EEA4BAA1431BB3583F1C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A73601A5D077FC21818DF1A7 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,7 +44,15 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 150A691AB39A996503500B6F /* 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 = ""; }; + 1F4AD80A94A4424BDAC710E6 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 38E3570849AA14FF6279A27B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3E49842AF400D749476AB5CC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 4E480D28DB430B25DE364E3B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 5CBACB91C0E2BD09B67EE252 /* 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -42,19 +63,51 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A73601A5D077FC21818DF1A7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E9F5293DC26577F542F9C25B /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6D78CDBF4DFDD3086E0BAF4C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E3DBFDC423C4C6577F4360A /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 56E6EEA4BAA1431BB3583F1C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0F5487B9C0F6C90C432CB1FE /* Pods */ = { + isa = PBXGroup; + children = ( + E9F5293DC26577F542F9C25B /* Pods-Runner.debug.xcconfig */, + 5CBACB91C0E2BD09B67EE252 /* Pods-Runner.release.xcconfig */, + 150A691AB39A996503500B6F /* Pods-Runner.profile.xcconfig */, + 38E3570849AA14FF6279A27B /* Pods-RunnerTests.debug.xcconfig */, + 3E49842AF400D749476AB5CC /* Pods-RunnerTests.release.xcconfig */, + 4E480D28DB430B25DE364E3B /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +125,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 0F5487B9C0F6C90C432CB1FE /* Pods */, + EA5AFD7C07F6FABF12C3E09B /* Frameworks */, ); sourceTree = ""; }; @@ -79,6 +135,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +147,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,26 +155,49 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { + EA5AFD7C07F6FABF12C3E09B /* Frameworks */ = { isa = PBXGroup; children = ( + A73601A5D077FC21818DF1A7 /* Pods_Runner.framework */, + 1F4AD80A94A4424BDAC710E6 /* Pods_RunnerTests.framework */, ); - name = "Supporting Files"; + name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 85CCB806A86D62B654B93B8C /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 6D78CDBF4DFDD3086E0BAF4C /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + ED9944BE5D3F9DE9E5EA7EC5 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + A82025EEB8538441842715CB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -135,9 +214,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -158,11 +242,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -179,10 +271,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -191,8 +285,31 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 85CCB806A86D62B654B93B8C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -205,9 +322,56 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A82025EEB8538441842715CB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + ED9944BE5D3F9DE9E5EA7EC5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +383,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +413,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +445,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +454,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -297,18 +470,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WV8V8CNBAX; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + INFOPLIST_KEY_CFBundleDisplayName = 1; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + MARKETING_VERSION = 1; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSmsExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -316,11 +488,61 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 38E3570849AA14FF6279A27B /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSmsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E49842AF400D749476AB5CC /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSmsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4E480D28DB430B25DE364E3B /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSmsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +572,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +587,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +597,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +629,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +638,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -431,18 +656,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WV8V8CNBAX; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + INFOPLIST_KEY_CFBundleDisplayName = 1; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + MARKETING_VERSION = 1; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSmsExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -458,18 +682,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WV8V8CNBAX; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + INFOPLIST_KEY_CFBundleDisplayName = 1; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + MARKETING_VERSION = 1; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSmsExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -480,6 +703,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a1..919434a 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..e3773d4 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + + + + + @@ -61,8 +73,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..6266644 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf0..7353c41 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd9..797d452 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b..6ed2d93 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde121..4cd7b00 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e..fe73094 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc230..321773c 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd9..797d452 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8..502f463 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b86..0ec3034 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b86..0ec3034 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d16..e9f5fea 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d..84ac32a 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41..8953cba 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f58..0467bf1 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index a060db6..4ec8ffe 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter Sms Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - example + flutter_sms_example CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart index d9eb179..3d3e1dd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,10 @@ import 'dart:async'; +import 'dart:developer' as developer; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_sms/flutter_sms.dart'; +import 'package:permission_handler/permission_handler.dart'; void main() => runApp(MyApp()); @@ -16,6 +19,14 @@ class _MyAppState extends State { String _canSendSMSMessage = 'Check is not run.'; List people = []; bool sendDirect = false; + bool _isLoading = false; + + Future requestSMSPermission() async { + var status = await Permission.sms.status; + if (!status.isGranted) { + await Permission.sms.request(); + } + } @override void initState() { @@ -23,29 +34,94 @@ class _MyAppState extends State { initPlatformState(); } + @override + void dispose() { + _controllerPeople.dispose(); + _controllerMessage.dispose(); + super.dispose(); + } + Future initPlatformState() async { + requestSMSPermission(); _controllerPeople = TextEditingController(); _controllerMessage = TextEditingController(); } Future _sendSMS(List recipients) async { + if (_isLoading) return; + + setState(() { + _isLoading = true; + _message = 'Sending SMS...'; + }); + try { + developer.log('Attempting to send SMS to: ${recipients.join(", ")}'); + developer.log('Message: ${_controllerMessage.text}'); + developer.log('Send Direct: $sendDirect'); + String _result = await sendSMS( message: _controllerMessage.text, recipients: recipients, sendDirect: sendDirect, ); - setState(() => _message = _result); + + developer.log('SMS Result: $_result'); + + setState(() { + _message = _result; + _isLoading = false; + }); + } on PlatformException catch (e) { + developer.log('PlatformException: ${e.code} - ${e.message}'); + setState(() { + _message = 'PlatformException: ${e.code} - ${e.message}'; + _isLoading = false; + }); } catch (error) { - setState(() => _message = error.toString()); + developer.log('Error sending SMS: $error'); + setState(() { + _message = 'Error: $error'; + _isLoading = false; + }); } } Future _canSendSMS() async { - bool _result = await canSendSMS(); - setState(() => _canSendSMSMessage = - _result ? 'This unit can send SMS' : 'This unit cannot send SMS'); - return _result; + if (_isLoading) return false; + + setState(() { + _isLoading = true; + _canSendSMSMessage = 'Checking...'; + }); + + try { + developer.log('Checking if device can send SMS...'); + bool _result = await canSendSMS(); + developer.log('Can send SMS result: $_result'); + + setState(() { + _canSendSMSMessage = + _result ? 'This unit can send SMS' : 'This unit cannot send SMS'; + _isLoading = false; + }); + return _result; + } on PlatformException catch (e) { + developer + .log('PlatformException in canSendSMS: ${e.code} - ${e.message}'); + setState(() { + _canSendSMSMessage = 'Error checking SMS capability: ${e.message}'; + _isLoading = false; + }); + return false; + } catch (error) { + developer.log('Error in canSendSMS: $error'); + setState(() { + _canSendSMSMessage = 'Error checking SMS capability: $error'; + _isLoading = false; + }); + return false; + } } Widget _phoneTile(String name) { @@ -53,12 +129,9 @@ class _MyAppState extends State { padding: const EdgeInsets.all(3), child: Container( decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.grey.shade300), - top: BorderSide(color: Colors.grey.shade300), - left: BorderSide(color: Colors.grey.shade300), - right: BorderSide(color: Colors.grey.shade300), - )), + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), child: Padding( padding: const EdgeInsets.all(4), child: Column( @@ -73,7 +146,7 @@ class _MyAppState extends State { padding: const EdgeInsets.all(0), child: Text( name, - textScaleFactor: 1, + textScaler: const TextScaler.linear(1), style: const TextStyle(fontSize: 12), ), ) @@ -90,9 +163,27 @@ class _MyAppState extends State { home: Scaffold( appBar: AppBar( title: const Text('SMS/MMS Example'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, ), body: ListView( children: [ + // Debug info section + Card( + margin: const EdgeInsets.all(8), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Debug Info:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text('Recipients: ${people.length}'), + Text('Message length: ${_controllerMessage.text.length}'), + Text('Send Direct: $sendDirect'), + ], + ), + ), + ), if (people.isEmpty) const SizedBox(height: 0) else @@ -114,7 +205,7 @@ class _MyAppState extends State { controller: _controllerPeople, decoration: const InputDecoration(labelText: 'Add Phone Number'), - keyboardType: TextInputType.number, + keyboardType: TextInputType.phone, onChanged: (String value) => setState(() {}), ), trailing: IconButton( @@ -133,6 +224,7 @@ class _MyAppState extends State { title: TextField( decoration: const InputDecoration(labelText: 'Add Message'), controller: _controllerMessage, + maxLines: 3, onChanged: (String value) => setState(() {}), ), ), @@ -140,18 +232,24 @@ class _MyAppState extends State { ListTile( title: const Text('Can send SMS'), subtitle: Text(_canSendSMSMessage), - trailing: IconButton( - padding: const EdgeInsets.symmetric(vertical: 16), - icon: const Icon(Icons.check), - onPressed: () { - _canSendSMS(); - }, - ), + trailing: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : IconButton( + padding: const EdgeInsets.symmetric(vertical: 16), + icon: const Icon(Icons.check), + onPressed: () { + _canSendSMS(); + }, + ), ), SwitchListTile( title: const Text('Send Direct'), subtitle: const Text( - 'Should we skip the additional dialog? (Android only)'), + 'Should we skip the additional dialog? (Android only - requires SMS permission)'), value: sendDirect, onChanged: (bool newValue) { setState(() { @@ -162,35 +260,68 @@ class _MyAppState extends State { padding: const EdgeInsets.all(8), child: ElevatedButton( style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (states) => Theme.of(context).colorScheme.secondary), - padding: MaterialStateProperty.resolveWith( - (states) => const EdgeInsets.symmetric(vertical: 16)), - ), - onPressed: () { - _send(); - }, - child: Text( - 'SEND', - style: Theme.of(context).textTheme.displayMedium, + backgroundColor: WidgetStateProperty.resolveWith( + (states) => Theme.of(context).colorScheme.primary), + padding: WidgetStateProperty.resolveWith( + (states) => const EdgeInsets.symmetric(vertical: 4)), ), + onPressed: _isLoading + ? null + : () { + _send(); + }, + child: _isLoading + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation(Colors.white), + ), + ), + SizedBox(width: 8), + Text( + 'SENDING...', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: Colors.white, + ), + ), + ], + ) + : Text( + 'SEND', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + ), + ), ), ), Visibility( visible: _message != null, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( + child: Card( + margin: const EdgeInsets.all(8), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Result:', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text( _message ?? 'No Data', maxLines: null, ), - ), + ], ), - ], + ), ), ), ], @@ -201,7 +332,9 @@ class _MyAppState extends State { void _send() { if (people.isEmpty) { - setState(() => _message = 'At Least 1 Person or Message Required'); + setState(() => _message = 'At least 1 phone number is required'); + } else if (_controllerMessage.text.trim().isEmpty) { + setState(() => _message = 'Message cannot be empty'); } else { _sendSMS(people); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0d61367..ba60b08 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,14 +1,16 @@ name: flutter_sms_example description: Demonstrates how to use the flutter_sms plugin. -publish_to: 'none' +publish_to: "none" environment: - sdk: '>=2.12.0-259.8.beta <3.0.0' + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: sdk: flutter + permission_handler: ^12.0.0+1 + dev_dependencies: flutter_sms: path: ../ @@ -17,4 +19,3 @@ dev_dependencies: flutter: uses-material-design: true - diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..3837770 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,85 @@ +// test/widget_test.dart +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// Since FlutterSms might not be available as package, define test class +class FlutterSms { + static const MethodChannel _channel = MethodChannel('flutter_sms'); + + static Future get canSendSMS async { + final bool result = await _channel.invokeMethod('canSendSMS'); + return result; + } + + static Future sendSMS({ + required String message, + required String recipients, + bool sendDirect = false, + }) async { + final String result = await _channel.invokeMethod('sendSMS', { + 'message': message, + 'recipients': recipients, + 'sendDirect': sendDirect, + }); + return result; + } +} + +void main() { + const MethodChannel channel = MethodChannel('flutter_sms'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + switch (methodCall.method) { + case 'canSendSMS': + return true; + case 'sendSMS': + return 'SMS Sent!'; + default: + return null; + } + }); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + group('Flutter SMS Tests', () { + test('canSendSMS returns true', () async { + final result = await FlutterSms.canSendSMS; + expect(result, true); + }); + + test('sendSMS returns success message', () async { + final result = await FlutterSms.sendSMS( + message: 'Test message', + recipients: '1234567890', + sendDirect: false, + ); + expect(result, 'SMS Sent!'); + }); + + test('sendSMS with direct sending', () async { + final result = await FlutterSms.sendSMS( + message: 'Direct test', + recipients: '1234567890', + sendDirect: true, + ); + expect(result, 'SMS Sent!'); + }); + + test('sendSMS with multiple recipients', () async { + final result = await FlutterSms.sendSMS( + message: 'Multi test', + recipients: '1234567890;0987654321', + sendDirect: false, + ); + expect(result, 'SMS Sent!'); + }); + }); +} diff --git a/ios/Classes/FlutterSmsPlugin.h b/ios/Classes/FlutterSmsPlugin.h index 6a7a959..60fbb05 100644 --- a/ios/Classes/FlutterSmsPlugin.h +++ b/ios/Classes/FlutterSmsPlugin.h @@ -1,4 +1,4 @@ #import -@interface FlutterSmsPlugin : NSObject -@end +@interface FlutterSmsPlugin : NSObject +@end \ No newline at end of file diff --git a/ios/Classes/FlutterSmsPlugin.m b/ios/Classes/FlutterSmsPlugin.m index df11e12..4ea5a05 100644 --- a/ios/Classes/FlutterSmsPlugin.m +++ b/ios/Classes/FlutterSmsPlugin.m @@ -1,8 +1,16 @@ #import "FlutterSmsPlugin.h" + +#if __has_include() #import +#else +// Fallback import +@import flutter_sms; +#endif @implementation FlutterSmsPlugin + + (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftFlutterSmsPlugin registerWithRegistrar:registrar]; + [SwiftFlutterSmsPlugin registerWithRegistrar:registrar]; } -@end + +@end \ No newline at end of file diff --git a/ios/Classes/SwiftFlutterSmsPlugin.swift b/ios/Classes/SwiftFlutterSmsPlugin.swift index 70cbc8a..8ca4243 100644 --- a/ios/Classes/SwiftFlutterSmsPlugin.swift +++ b/ios/Classes/SwiftFlutterSmsPlugin.swift @@ -3,70 +3,87 @@ import UIKit import MessageUI public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, UINavigationControllerDelegate, MFMessageComposeViewControllerDelegate { - var result: FlutterResult? - 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) - } - - 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 - - case "canSendSMS": - #if targetEnvironment(simulator) - result(false) - #else - if (MFMessageComposeViewController.canSendText()) { - result(true) - } else { - result(false) + private var result: FlutterResult? + private 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) + } + + 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 + + // Updated way to get the root view controller + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }), + let rootViewController = window.rootViewController { + + // Find the topmost presented view controller + var topController = rootViewController + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + + topController.present(controller, animated: true, completion: nil) + } else { + result(FlutterError( + code: "no_view_controller", + message: "Could not find a view controller to present the message composer.", + details: "Unable to access the root view controller for presenting the SMS interface." + )) + } + } 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 + + case "canSendSMS": + #if targetEnvironment(simulator) + result(false) + #else + result(MFMessageComposeViewController.canSendText()) + #endif + + default: + result(FlutterMethodNotImplemented) } - #endif - - default: - result(FlutterMethodNotImplemented) - break } - } - - public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { - let map: [MessageComposeResult: String] = [ - MessageComposeResult.sent: "sent", - MessageComposeResult.cancelled: "cancelled", - MessageComposeResult.failed: "failed", - ] - if let callback = self.result { - callback(map[result]) + + public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { + let resultMap: [MessageComposeResult: String] = [ + .sent: "sent", + .cancelled: "cancelled", + .failed: "failed" + ] + + if let callback = self.result { + callback(resultMap[result] ?? "unknown") + self.result = nil // Clear the callback to prevent memory leaks + } + + // Updated dismissal method + controller.dismiss(animated: true, completion: nil) } - UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil) - } -} +} \ No newline at end of file diff --git a/ios/flutter_sms.podspec b/ios/flutter_sms.podspec index 46476f7..2fd3193 100644 --- a/ios/flutter_sms.podspec +++ b/ios/flutter_sms.podspec @@ -1,23 +1,40 @@ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint flutter_sms.podspec' to validate before publishing. +# Run `pod lib lint flutter_sms.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'flutter_sms' - s.version = '1.1.0' + s.version = '2.0.0' s.summary = 'A Flutter plugin for Sending SMS on Android and iOS.' s.description = <<-DESC -A new flutter plugin project. +A Flutter plugin that provides SMS sending functionality for both Android and iOS platforms. +Supports both direct SMS sending and SMS dialog interface. DESC s.homepage = 'https://github.com/fluttercommunity/flutter_sms' s.license = { :file => '../LICENSE' } s.author = { 'Rody Davis' => 'rody.davis.jr@gmail.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.platform = :ios, '8.0' - - # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } -end + + # Updated platform requirements + s.platform = :ios, '15.0' + s.ios.deployment_target = '15.0' + + # Updated architecture support + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'SWIFT_VERSION' => '5.0' + } + + # Swift version specification + s.swift_version = '5.0' + + # Add required frameworks + s.frameworks = 'MessageUI' + + # Weak frameworks for optional features + s.weak_frameworks = 'UserNotifications' +end \ No newline at end of file diff --git a/lib/src/flutter_sms_platform.dart b/lib/src/flutter_sms_platform.dart index ff4361e..f083d3d 100644 --- a/lib/src/flutter_sms_platform.dart +++ b/lib/src/flutter_sms_platform.dart @@ -6,7 +6,8 @@ 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'; +// Assuming 'user_agent/io.dart' and 'user_agent/web.dart' are part of your plugin +// and handle user agent sniffing if needed for web. const MethodChannel _channel = MethodChannel('flutter_sms'); @@ -16,73 +17,133 @@ class FlutterSmsPlatform extends PlatformInterface { static final Object _token = Object(); - static FlutterSmsPlatform _instance = FlutterSmsPlatform(); + static FlutterSmsPlatform _instance = + MethodChannelFlutterSms(); // Initialize with the default MethodChannel implementation /// The default instance of [FlutterSmsPlatform] to use. /// - /// Defaults to [MethodChannelFlutterSmsPlatform]. + /// Defaults to [MethodChannelFlutterSms]. 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; } + /// Sends an SMS message to a list of recipients. /// - /// + /// The [message] is the body of the SMS. + /// The [recipients] is a list of phone numbers. + /// [sendDirect] (Android only) attempts to send the SMS directly without + /// opening the messaging app. This might require additional permissions. Future sendSMS({ required String message, required List recipients, bool sendDirect = false, }) { - final mapData = {}; - mapData['message'] = message; + throw UnimplementedError('sendSMS() has not been implemented.'); + } + + /// Checks if the device can send SMS messages. + Future canSendSMS() { + throw UnimplementedError('canSendSMS() has not been implemented.'); + } + + /// Launches the default SMS application with multiple recipients and an optional body. + /// + /// Returns `true` if the SMS app was launched, `false` otherwise. + Future launchSmsMulti(List numbers, [String? body]) { + throw UnimplementedError('launchSmsMulti() has not been implemented.'); + } + + /// Launches the default SMS application with a single recipient and an optional body. + /// + /// Returns `true` if the SMS app was launched, `false` otherwise. + Future launchSms(String? number, [String? body]) { + throw UnimplementedError('launchSms() has not been implemented.'); + } +} + +/// An implementation of [FlutterSmsPlatform] that uses method channels. +class MethodChannelFlutterSms extends FlutterSmsPlatform { + @override + Future sendSMS({ + required String message, + required List recipients, + bool sendDirect = false, + }) async { + final Map arguments = { + 'message': message, + }; + if (!kIsWeb && Platform.isIOS) { - mapData['recipients'] = recipients; - return _channel - .invokeMethod('sendSMS', mapData) - .then((value) => value ?? 'Error sending sms'); + arguments['recipients'] = recipients; } else { - String _phones = recipients.join(';'); - mapData['recipients'] = _phones; - mapData['sendDirect'] = sendDirect; - return _channel - .invokeMethod('sendSMS', mapData) - .then((value) => value ?? 'Error sending sms'); + // For Android and other platforms, join recipients with ';' + arguments['recipients'] = recipients.join(';'); + arguments['sendDirect'] = sendDirect; + } + + try { + final String? result = + await _channel.invokeMethod('sendSMS', arguments); + return result ?? 'Error sending sms'; + } on PlatformException catch (e) { + return 'Failed to send SMS: ${e.message}'; } } - Future canSendSMS() { - return _channel - .invokeMethod('canSendSMS') - .then((value) => value ?? false); + @override + Future canSendSMS() async { + try { + final bool? result = await _channel.invokeMethod('canSendSMS'); + return result ?? false; + } on PlatformException catch (e) { + debugPrint('Error checking SMS capability: ${e.message}'); + return false; + } } - Future launchSmsMulti(List numbers, [String? body]) { + @override + Future launchSmsMulti(List numbers, [String? body]) async { if (numbers.length == 1) { return launchSms(numbers.first, body); } - String _phones = numbers.join(';'); + final String phones = numbers.join(';'); + String uri = 'sms:/open?addresses=$phones'; if (body != null) { - final _body = Uri.encodeComponent(body); - return launch('sms:/open?addresses=$_phones${separator}body=$_body'); + final String encodedBody = Uri.encodeComponent(body); + uri += '${_getSeparator()}body=$encodedBody'; } - return launch('sms:/open?addresses=$_phones'); + return _launchUrl(uri); } - Future launchSms(String? number, [String? body]) { - // ignore: parameter_assignments - number ??= ''; + @override + Future launchSms(String? number, [String? body]) async { + final String actualNumber = number ?? ''; + String uri = 'sms:/$actualNumber'; if (body != null) { - final _body = Uri.encodeComponent(body); - return launch('sms:/$number${separator}body=$_body'); + final String encodedBody = Uri.encodeComponent(body); + uri += '${_getSeparator()}body=$encodedBody'; } - return launch('sms:/$number'); + return _launchUrl(uri); + } + + // Helper to determine the URL separator for SMS intents + String _getSeparator() { + return kIsWeb ? '&' : (Platform.isIOS ? '&' : '?'); } - String get separator => isCupertino() ? '&' : '?'; + // Helper to launch URLs using url_launcher + Future _launchUrl(String url) async { + final Uri uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + return await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + debugPrint('Could not launch $url'); + return false; + } + } } diff --git a/lib/src/user_agent/web.dart b/lib/src/user_agent/web.dart index c6a7a33..b9a4fa8 100644 --- a/lib/src/user_agent/web.dart +++ b/lib/src/user_agent/web.dart @@ -1,4 +1,4 @@ -import 'dart:html' as html; +import 'package:web/web.dart' as web; bool isCupertino() { final _devices = [ @@ -10,7 +10,8 @@ bool isCupertino() { 'iPod', 'Mac OS X', ]; - final String _agent = html.window.navigator.userAgent; + + final String _agent = web.window.navigator.userAgent; for (final device in _devices) { if (_agent.contains(device)) { return true; diff --git a/pubspec.yaml b/pubspec.yaml index 7051335..5957ec7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,21 +1,21 @@ 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 +version: 2.4.0 homepage: https://github.com/rodydavis/plugins repository: https://github.com/fluttercommunity/flutter_sms maintainer: Rody Davis (@rodydavis) environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ^1.10.0 + sdk: ">=3.0.0 <4.0.0" + flutter: ^3.10.0 dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter - plugin_platform_interface: ^2.0.0 - url_launcher: ^6.0.3 + plugin_platform_interface: ^2.1.8 + url_launcher: ^6.3.1 dev_dependencies: flutter_test: