diff --git a/android/build.gradle b/android/build.gradle
index e25d58e..5c77876 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
diff --git a/android/src/main/kotlin/co/sunnyapp/flutter_phone_state/FlutterPhoneStatePlugin.kt b/android/src/main/kotlin/co/sunnyapp/flutter_phone_state/FlutterPhoneStatePlugin.kt
index 7fc1ea4..6107817 100644
--- a/android/src/main/kotlin/co/sunnyapp/flutter_phone_state/FlutterPhoneStatePlugin.kt
+++ b/android/src/main/kotlin/co/sunnyapp/flutter_phone_state/FlutterPhoneStatePlugin.kt
@@ -4,30 +4,40 @@ import android.content.Context
import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import io.flutter.plugin.common.EventChannel
+import io.flutter.embedding.engine.plugins.FlutterPlugin
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
+import androidx.annotation.NonNull
-class FlutterPhoneStatePlugin(context: Context) : MethodCallHandler, EventChannel.StreamHandler {
- companion object {
- @JvmStatic
- fun registerWith(registrar: Registrar) {
- val channel = MethodChannel(registrar.messenger(), "flutter_phone_state")
- val plugin = FlutterPhoneStatePlugin(registrar.context())
- channel.setMethodCallHandler(plugin)
+class FlutterPhoneStatePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
+ private lateinit var channel: MethodChannel
+ private lateinit var eventSink: EventChannel
+ private lateinit var binding: FlutterPlugin.FlutterPluginBinding
+ private lateinit var context: Context
- val eventSink = EventChannel(registrar.messenger(), "co.sunnyapp/phone_events")
- eventSink.setStreamHandler(plugin)
- }
- }
-
- private val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+ private lateinit var telephonyManager: TelephonyManager
/// So it doesn't get collected
private lateinit var listener:PhoneStateListener
+ override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ binding = flutterPluginBinding
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_phone_state")
+ channel.setMethodCallHandler(this)
+ eventSink = EventChannel(flutterPluginBinding.binaryMessenger, "co.sunnyapp/phone_events")
+ eventSink.setStreamHandler(this)
+ context = flutterPluginBinding.applicationContext
+ telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+ }
+
+
+ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ eventSink.setStreamHandler(null)
+ }
+
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 5b9135f..7c69198 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -6,7 +6,7 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
+
diff --git a/example/android/app/src/main/kotlin/co/sunnyapp/flutter_phone_state_example/MainActivity.kt b/example/android/app/src/main/kotlin/co/sunnyapp/flutter_phone_state_example/MainActivity.kt
index 300c6d5..08004c0 100644
--- a/example/android/app/src/main/kotlin/co/sunnyapp/flutter_phone_state_example/MainActivity.kt
+++ b/example/android/app/src/main/kotlin/co/sunnyapp/flutter_phone_state_example/MainActivity.kt
@@ -1,12 +1,9 @@
package co.sunnyapp.flutter_phone_state_example
-import android.os.Bundle
-import io.flutter.app.FlutterActivity
-import io.flutter.plugins.GeneratedPluginRegistrant
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.plugins.firebase.core.FirebaseCorePlugin;
class MainActivity: FlutterActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- GeneratedPluginRegistrant.registerWith(this)
- }
+
}
diff --git a/example/android/build.gradle b/example/android/build.gradle
index 3100ad2..c505a86 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
diff --git a/example/ios/Podfile b/example/ios/Podfile
index b30a428..1e8c3c9 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -10,81 +10,32 @@ 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
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
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.
-
- 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'];
-
- 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
-
- # 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')
- end
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
-# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
-install! 'cocoapods', :disable_input_output_paths => true
-
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/Podfile.lock b/example/ios/Podfile.lock
index 7fa8557..9b683b7 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -19,10 +19,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher/ios"
SPEC CHECKSUMS:
- Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
+ Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_phone_state: ebb27962d725cbe0bdf498654c2a9083acf91b75
- url_launcher: a1c0cc845906122c4784c542523d8cacbded5626
+ url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
-PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83
+PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
-COCOAPODS: 1.8.4
+COCOAPODS: 1.10.1
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index df5b730..52fc0f8 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -9,12 +9,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
5510357B74253A2A913E4BDA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B14E8BC5A505DE4661E9079 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
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 */; };
@@ -27,8 +23,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -39,7 +33,6 @@
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 = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
5B2F318F1C214126BF760093 /* 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 = ""; };
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 = ""; };
@@ -47,7 +40,6 @@
955826ACA8BDC5D39FFBC095 /* 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 = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -62,8 +54,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
5510357B74253A2A913E4BDA /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -92,9 +82,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
- 3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -230,7 +218,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
@@ -274,9 +262,14 @@
files = (
);
inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/flutter_phone_state/flutter_phone_state.framework",
+ "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_phone_state.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -319,7 +312,6 @@
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -397,7 +389,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -453,7 +444,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
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/lib/main.dart b/example/lib/main.dart
index 5a8a099..96f8bbd 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -18,11 +18,11 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State {
- List _rawEvents;
- List _phoneEvents;
+ late List _rawEvents;
+ late List _phoneEvents;
/// The result of the user typing
- String _phoneNumber;
+ String? _phoneNumber;
@override
void initState() {
@@ -31,7 +31,7 @@ class _MyAppState extends State {
_rawEvents = _accumulate(FlutterPhoneState.rawPhoneEvents);
}
- List _accumulate(Stream input) {
+ List _accumulate(Stream input) {
final items = [];
input.forEach((item) {
if (item != null) {
@@ -44,8 +44,7 @@ class _MyAppState extends State {
}
/// Extracts a list of phone calls from the accumulated events
- Iterable get _completedCalls =>
- Map.fromEntries(_phoneEvents.reversed.map((PhoneCallEvent event) {
+ Iterable get _completedCalls => Map.fromEntries(_phoneEvents.reversed.map((PhoneCallEvent event) {
return MapEntry(event.call.id, event.call);
})).values.where((c) => c.isComplete).toList();
@@ -64,29 +63,26 @@ class _MyAppState extends State {
flex: 1,
child: TextField(
onChanged: (v) => _phoneNumber = v,
- decoration: InputDecoration(labelText: "Phone number"),
+ decoration: InputDecoration(labelText: 'Phone number'),
)),
MaterialButton(
onPressed: () => _initiateCall(),
- child: Text("Make Call", style: TextStyle(color: Colors.white)),
color: Colors.blue,
+ child: Text('Make Call', style: TextStyle(color: Colors.white)),
),
]),
verticalSpace,
- _title("Current Calls"),
- for (final call in FlutterPhoneState.activeCalls)
- _CallCard(phoneCall: call),
- if (FlutterPhoneState.activeCalls.isEmpty)
- Center(child: Text("No Active Calls")),
- _title("Call History"),
+ _title('Current Calls'),
+ for (final call in FlutterPhoneState.activeCalls) _CallCard(phoneCall: call),
+ if (FlutterPhoneState.activeCalls.isEmpty) Center(child: Text('No Active Calls')),
+ _title('Call History'),
for (final call in _completedCalls)
_CallCard(
phoneCall: call,
),
- if (_completedCalls.isEmpty)
- Center(child: Text("No Completed Calls")),
+ if (_completedCalls.isEmpty) Center(child: Text('No Completed Calls')),
verticalSpace,
- _title("Raw Event History"),
+ _title('Raw Event History'),
if (_rawEvents.isNotEmpty)
Padding(
padding: EdgeInsets.all(10),
@@ -94,12 +90,12 @@ class _MyAppState extends State {
children: [
TableRow(children: [
Text(
- "id",
+ 'id',
style: listHeaderStyle,
maxLines: 1,
),
- Text("number", style: listHeaderStyle),
- Text("event", style: listHeaderStyle),
+ Text('number', style: listHeaderStyle),
+ Text('event', style: listHeaderStyle),
]),
for (final event in _rawEvents)
TableRow(children: [
@@ -110,7 +106,7 @@ class _MyAppState extends State {
],
),
),
- if (_rawEvents.isEmpty) Center(child: Text("No Raw Events")),
+ if (_rawEvents.isEmpty) Center(child: Text('No Raw Events')),
],
),
),
@@ -132,10 +128,11 @@ class _MyAppState extends State {
child: Text(text?.toString() ?? '-', maxLines: 1, style: headerStyle));
}
- _initiateCall() {
- if (_phoneNumber?.isNotEmpty == true) {
+ Future _initiateCall() async {
+ final phoneNumber = _phoneNumber;
+ if (phoneNumber != null && phoneNumber.isNotEmpty) {
setState(() {
- FlutterPhoneState.startPhoneCall(_phoneNumber);
+ FlutterPhoneState.startPhoneCall(phoneNumber);
});
}
}
@@ -144,15 +141,17 @@ class _MyAppState extends State {
class _CallCard extends StatelessWidget {
final PhoneCall phoneCall;
- const _CallCard({Key key, this.phoneCall}) : super(key: key);
+ const _CallCard({
+ Key? key,
+ required this.phoneCall,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
dense: true,
- leading: Icon(
- phoneCall.isOutbound ? Icons.arrow_upward : Icons.arrow_downward),
+ leading: Icon(phoneCall.isOutbound ? Icons.arrow_upward : Icons.arrow_downward),
title: Text(
"+${phoneCall.phoneNumber ?? "Unknown number"}: ${value(phoneCall.status)}",
overflow: TextOverflow.visible,
@@ -160,11 +159,10 @@ class _CallCard extends StatelessWidget {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (phoneCall.id?.isNotEmpty == true)
- Text("id: ${truncate(phoneCall.id, 12)}"),
+ if (phoneCall.id.isNotEmpty) Text('id: ${truncate(phoneCall.id, 12)}'),
for (final event in phoneCall.events)
Text(
- "- ${value(event.status) ?? "-"}",
+ '- ${value(event.status)}',
maxLines: 1,
),
],
@@ -172,7 +170,7 @@ class _CallCard extends StatelessWidget {
trailing: FutureBuilder(
builder: (context, snap) {
if (snap.hasData && snap.data?.isComplete == true) {
- return Text("${phoneCall.duration?.inSeconds ?? '?'}s");
+ return Text('${phoneCall.duration.inSeconds}s');
} else {
return CircularProgressIndicator();
}
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 6851511..c9125f2 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -7,56 +7,49 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.5.0-nullsafety.1"
+ version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.3"
+ version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.15.0-nullsafety.3"
- convert:
- dependency: transitive
- description:
- name: convert
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.1"
+ version: "1.15.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.3"
+ version: "3.0.1"
cupertino_icons:
dependency: "direct main"
description:
@@ -70,7 +63,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
@@ -82,7 +75,7 @@ packages:
path: ".."
relative: true
source: path
- version: "0.5.9"
+ version: "2.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -100,55 +93,55 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.0"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.6.3"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
- version: "0.11.4"
+ version: "1.0.1"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.10-nullsafety.1"
+ version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.1"
- platform_detect:
- dependency: transitive
+ version: "1.8.0"
+ pedantic:
+ dependency: "direct dev"
description:
- name: platform_detect
+ name: pedantic
url: "https://pub.dartlang.org"
source: hosted
- version: "1.4.0"
+ version: "1.11.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.2"
- pub_semver:
- dependency: transitive
- description:
- name: pub_semver
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.4.4"
+ version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
@@ -160,105 +153,112 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.2"
+ version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
- version: "1.10.0-nullsafety.1"
+ version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.19-nullsafety.2"
+ version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
url_launcher:
dependency: transitive
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
- version: "5.5.0"
+ version: "6.0.6"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
- version: "0.0.1+1"
+ version: "2.0.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
- version: "0.0.1+7"
+ version: "2.0.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.7"
+ version: "2.0.3"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
- version: "0.1.2"
+ version: "2.0.0"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.4"
+ version: "3.0.4"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.3"
+ version: "2.1.0"
sdks:
- dart: ">=2.10.0-110 <2.11.0"
- flutter: ">=1.12.13+hotfix.5 <2.0.0"
+ dart: ">=2.12.0 <3.0.0"
+ flutter: ">=2.0.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index a7d1b6d..4729abd 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,25 +1,26 @@
name: flutter_phone_state_example
description: Demonstrates how to use the flutter_phone_state plugin.
publish_to: 'none'
+version: 2.0.0
environment:
- sdk: ">=2.2.2 <3.0.0"
+ sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
- intl:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^0.1.2
+ cupertino_icons:
+ intl:
dev_dependencies:
flutter_test:
sdk: flutter
-
flutter_phone_state:
path: ../
+ pedantic: ^1.11.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart
index c878c7d..a4797e1 100644
--- a/example/test/widget_test.dart
+++ b/example/test/widget_test.dart
@@ -18,8 +18,7 @@ void main() {
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
- (Widget widget) =>
- widget is Text && widget.data.startsWith('Running on:'),
+ (Widget widget) => widget is Text && widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
diff --git a/lib/extensions.dart b/lib/extensions.dart
deleted file mode 100644
index f010a4f..0000000
--- a/lib/extensions.dart
+++ /dev/null
@@ -1,45 +0,0 @@
-//extension DateTimeExt on DateTime {
-// Duration sinceNow() => -(this.difference(DateTime.now()));
-//}
-//
-//extension IterableExtension on List {
-// X find([bool filter(X input)]) {
-// return this?.firstWhere(filter, orElse: () => null);
-// }
-//
-// X lastOrNull([bool filter(X input)]) {
-// return this?.lastWhere(filter, orElse: () => null);
-// }
-//
-// X firstOrNull([bool filter(X input)]) {
-// return this?.firstWhere(filter, orElse: () => null);
-// }
-//}
-//
-//extension StringExt on String {
-// String truncate(int length) {
-// if (this == null) return this;
-// if (this.length <= length) {
-// return this;
-// } else {
-// return this.substring(0, length);
-// }
-// }
-//
-// bool get isNullOrEmpty => this?.isNotEmpty != true;
-//
-// bool get isNotNullOrEmpty => !isNullOrEmpty;
-//
-// bool get isNullOrBlank => this == null || this.trim().isEmpty == true;
-//
-// bool get isNotNullOrBlank => !isNullOrBlank;
-//
-// String orEmpty() {
-// if (this == null) return "";
-// return this;
-// }
-//}
-//
-//extension EnumExtension on Object {
-// String get value => "$this".replaceAll(RegExp(".*\\."), "");
-//}
diff --git a/lib/extensions_static.dart b/lib/extensions_static.dart
index 196a2e4..cb9b8b8 100644
--- a/lib/extensions_static.dart
+++ b/lib/extensions_static.dart
@@ -1,18 +1,26 @@
+import 'package:collection/collection.dart';
+
Duration sinceNow(DateTime self) => -(self.difference(DateTime.now()));
-X find(List self, [bool filter(X input)]) {
- return self?.firstWhere(filter, orElse: () => null);
+X? find(List? self, bool Function(X? input) filter) {
+ return self?.firstWhereOrNull(
+ filter,
+ );
}
-X lastOrNull(List self, [bool filter(X input)]) {
- return self?.lastWhere(filter, orElse: () => null);
+X? lastOrNull(List? self, bool Function(X? input) filter) {
+ return self?.lastWhereOrNull(
+ filter,
+ );
}
-X firstOrNull(List self, [bool filter(X input)]) {
- return self?.firstWhere(filter, orElse: () => null);
+X? firstOrNull(List? self, bool Function(X? input) filter) {
+ return self?.firstWhereOrNull(
+ filter,
+ );
}
-String truncate(String self, int length) {
+String? truncate(String? self, int length) {
if (self == null) return self;
if (self.length <= length) {
return self;
@@ -21,19 +29,19 @@ String truncate(String self, int length) {
}
}
-bool isNullOrEmpty(String self) {
+bool isNullOrEmpty(String? self) {
return self?.isNotEmpty != true;
}
bool isNotNullOrEmpty(String self) => isNullOrEmpty(self);
-bool isNullOrBlank(String self) => self == null || self.trim().isEmpty == true;
+bool isNullOrBlank(String? self) => self == null || self.trim().isEmpty == true;
bool isNotNullOrBlank(String self) => !isNullOrBlank(self);
-String orEmpty(String self) {
- if (self == null) return "";
+String orEmpty(String? self) {
+ if (self == null) return '';
return self;
}
-String value(self) => "$self".replaceAll(RegExp(".*\\."), "");
+String value(self) => '$self'.replaceAll(RegExp('.*\\.'), '');
diff --git a/lib/flutter_phone_state.dart b/lib/flutter_phone_state.dart
index bb4aca1..fc292ae 100644
--- a/lib/flutter_phone_state.dart
+++ b/lib/flutter_phone_state.dart
@@ -1,12 +1,11 @@
import 'dart:async';
-
-import 'package:flutter/services.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_phone_state/extensions_static.dart';
-import 'package:flutter_phone_state/logging.dart';
-import 'package:flutter_phone_state/phone_event.dart';
import 'package:logging/logging.dart';
import 'package:url_launcher/url_launcher.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'extensions_static.dart';
+import 'logging.dart';
+import 'phone_event.dart';
export 'package:flutter_phone_state/phone_event.dart';
@@ -14,22 +13,22 @@ export 'package:flutter_phone_state/phone_event.dart';
final _localEvents = StreamController.broadcast();
const MethodChannel _channel = MethodChannel('flutter_phone_state');
-final Logger _log = Logger("flutterPhoneState");
+final Logger _log = Logger('flutterPhoneState');
final _instance = FlutterPhoneState();
class FlutterPhoneState with WidgetsBindingObserver {
/// Configures logging. FlutterPhoneState uses the [logging] plugin.
- static void configureLogs({Level level, Logging onLog}) {
+ static void configureLogs({Level? level, Logging? onLog}) {
configureLogging(logger: _log, level: level, onLog: onLog);
}
static Future get platformVersion async {
- final String version = await _channel.invokeMethod('getPlatformVersion');
+ final version = await _channel.invokeMethod('getPlatformVersion') as String;
return version;
}
/// A broadcast stream of raw events from the underlying phone state. It's preferred to use [phoneCallEvents]
- static Stream get rawPhoneEvents => _initializedNativeEvents;
+ static Stream get rawPhoneEvents => _initializedNativeEvents;
/// A list of events associated to all calls. This includes events from the underlying OS, as well as our
/// own cancellation and timeout errors
@@ -47,7 +46,7 @@ class FlutterPhoneState with WidgetsBindingObserver {
FlutterPhoneState() {
configureLogging(logger: _log);
- WidgetsBinding.instance.addObserver(this);
+ WidgetsBinding.instance?.addObserver(this);
_initializedNativeEvents.forEach(_handleRawPhoneEvent);
}
@@ -55,16 +54,17 @@ class FlutterPhoneState with WidgetsBindingObserver {
/// This should add both calls, and track them separately as best we can.
///
/// As a note, Android does not support listening to events from nested calls.
- List _calls = [];
+ final List _calls = [];
/// Finds a previously placed call that matches the incoming event
- PhoneCall _findMatchingCall(RawPhoneEvent event) {
+ PhoneCall? _findMatchingCall(RawPhoneEvent event) {
// Either the first matching, or the first one without an ID
- PhoneCall matching;
+ PhoneCall? matching;
if (event.id != null) {
- matching = firstOrNull(_calls, (c) => c.callId == event.id);
+ matching = firstOrNull(_calls, (c) => c?.callId == event.id);
}
- matching ??= lastOrNull(_calls, (call) => call.canBeLinked(event));
+ matching ??=
+ lastOrNull(_calls, (call) => call?.canBeLinked(event) ?? false);
if (matching != null) {
// Link them together for future reference
matching.callId = event.id;
@@ -72,15 +72,20 @@ class FlutterPhoneState with WidgetsBindingObserver {
return matching;
}
+ @override
void didChangeAppLifecycleState(AppLifecycleState state) {
- _log.info("Received application lifecycle state change: $state");
+ _log.info('Received application lifecycle state change: $state');
if (state == AppLifecycleState.resumed) {
/// We wait 1 second because ios has a short flash of resumed before the phone app opens
Future.delayed(Duration(seconds: 1), () {
- final expired = lastOrNull(_calls, (PhoneCall c) {
- return c.status == PhoneCallStatus.dialing &&
- sinceNow(c.startTime).inSeconds < 30;
+ final expired = lastOrNull(_calls, (PhoneCall? c) {
+ if (c != null) {
+ return c.status == PhoneCallStatus.dialing &&
+ sinceNow(c.startTime).inSeconds < 30;
+ } else {
+ return false;
+ }
});
if (expired != null) {
@@ -90,7 +95,7 @@ class FlutterPhoneState with WidgetsBindingObserver {
}
}
- _openCallLink(PhoneCall call) async {
+ Future _openCallLink(PhoneCall call) async {
/// Phone calls are weird in IOS. We need to initiate the phone call by using the link
/// below, but the app doesn't give us any meaningful feedback, so we mark the phone interaction
/// as "complete" (technically this just means the call was started) by either
@@ -99,7 +104,7 @@ class FlutterPhoneState with WidgetsBindingObserver {
/// (c) 5 seconds passes with no feedback (this will send back a result code of [cancelled], which
/// means the call won't be logged
try {
- final link = "tel:${call.phoneNumber}";
+ final link = 'tel:${call.phoneNumber}';
final status = await _openTelLink(link);
if (status != LinkOpenResult.success) {
@@ -130,15 +135,15 @@ class FlutterPhoneState with WidgetsBindingObserver {
// create an event
PhoneCallEvent event;
if (call.events.any((e) => e.status == status)) {
- _log.fine("Call ${truncate(call.id, 8)} already has status $status");
+ _log.fine('Call ${truncate(call.id, 8)} already has status $status');
}
if (status == PhoneCallStatus.disconnected ||
status == PhoneCallStatus.timedOut ||
status == PhoneCallStatus.error ||
status == PhoneCallStatus.cancelled) {
- _log.info("Call is done: ${call.id}- Removing due to $status");
+ _log.info('Call is done: ${call.id}- Removing due to $status');
call.complete(status).then((event) {
- _localEvents.add(event);
+ _localEvents.add(event!);
});
_calls.removeWhere((existing) => existing == call);
} else {
@@ -147,14 +152,16 @@ class FlutterPhoneState with WidgetsBindingObserver {
}
}
- _handleRawPhoneEvent(RawPhoneEvent event) async {
+ Future _handleRawPhoneEvent(RawPhoneEvent? event) async {
+ if (event == null) return;
+
try {
- _pruneCalls();
- PhoneCall matching = _findMatchingCall(event);
+ await _pruneCalls();
+ var matching = _findMatchingCall(event);
/// If no match was found?
if (matching == null && event.isNewCall) {
- _log.info("Adding a call to the stack: $event");
+ _log.info('Adding a call to the stack: $event');
matching = PhoneCall.start(
event.phoneNumber,
event.type == RawEventType.inbound
@@ -192,12 +199,12 @@ class FlutterPhoneState with WidgetsBindingObserver {
break;
}
} catch (e, stack) {
- _log.severe("Error handling phone call event: $e", e, stack);
+ _log.severe('Error handling phone call event: $e', e, stack);
}
}
/// Looks for calls that weren't properly terminated and completes them
- _pruneCalls() {
+ Future _pruneCalls() async {
final expired = [..._calls.where((c) => c.isExpired)];
for (final expiring in expired) {
_changeStatus(expiring, PhoneCallStatus.timedOut);
@@ -210,48 +217,52 @@ final EventChannel _phoneStateCallEventChannel =
EventChannel('co.sunnyapp/phone_events');
/// Native event stream, lazily created. See [nativeEvents]
-Stream _nativeEvents;
+Stream? _nativeEvents;
/// A stream of [RawPhoneEvent] instances. The stream only contains null values if there was an error
-Stream get _initializedNativeEvents {
+Stream get _initializedNativeEvents {
_nativeEvents ??=
_phoneStateCallEventChannel.receiveBroadcastStream().map((dyn) {
try {
if (dyn == null) return null;
if (dyn is! Map) {
- _log.warning("Unexpected result type for phone event. "
+ _log.warning('Unexpected result type for phone event. '
"Expected Map but got ${dyn?.runtimeType ?? 'null'} ");
}
- final Map event = (dyn as Map).cast();
- final eventType = _parseEventType(event["type"] as String);
+ final event = Map.from(dyn as Map);
return RawPhoneEvent(
- event["id"] as String, event["phoneNumber"] as String, eventType);
+ (event['id'] is String) ? event['id'] as String : null,
+ (event['phoneNumber'] is String)
+ ? event['phoneNumber'] as String
+ : null,
+ _parseEventType(event['type'] as String),
+ );
} catch (e, stack) {
- _log.severe("Error handling native event $e", e, stack);
+ _log.severe('Error handling native event $e', e, stack);
return null;
}
});
- return _nativeEvents;
+ return _nativeEvents!;
}
RawEventType _parseEventType(String dyn) {
switch (dyn) {
- case "inbound":
+ case 'inbound':
return RawEventType.inbound;
- case "connected":
+ case 'connected':
return RawEventType.connected;
- case "outbound":
+ case 'outbound':
return RawEventType.outbound;
- case "disconnected":
+ case 'disconnected':
return RawEventType.disconnected;
default:
- throw "Illegal raw event type: $dyn";
+ throw 'Illegal raw event type: $dyn';
}
}
/// Removes all non-numeric characters
String sanitizePhoneNumber(String input) {
- String out = "";
+ var out = '';
for (var i = 0; i < input.length; ++i) {
var char = input[i];
@@ -262,14 +273,14 @@ String sanitizePhoneNumber(String input) {
return out;
}
-bool _isNumeric(String str) {
+bool _isNumeric(String? str) {
if (str == null) {
return false;
}
return double.tryParse(str) != null;
}
-Future _openTelLink(String appLink) async {
+Future _openTelLink(String? appLink) async {
if (appLink == null) {
return LinkOpenResult.invalidInput;
}
diff --git a/lib/logging.dart b/lib/logging.dart
index d0c918d..ecd91ea 100644
--- a/lib/logging.dart
+++ b/lib/logging.dart
@@ -7,10 +7,14 @@ import 'package:logging/logging.dart';
typedef Logging = void Function(LogRecord record);
/// Keeps the old logging subscription, so we can cancel at reconfigure
-StreamSubscription _subscription;
+StreamSubscription? _subscription;
/// Configures a single logger. By default will
-configureLogging({Logger logger, Level level, Logging onLog}) async {
+Future configureLogging({
+ Logger? logger,
+ Level? level,
+ Logging? onLog,
+}) async {
level ??= Level.INFO;
logger ??= Logger.root;
onLog ??= defaultLogging(logger);
diff --git a/lib/phone_event.dart b/lib/phone_event.dart
index 872abfa..32fc3c6 100644
--- a/lib/phone_event.dart
+++ b/lib/phone_event.dart
@@ -1,10 +1,10 @@
import 'dart:async';
-import 'package:flutter_phone_state/extensions_static.dart';
import 'package:logging/logging.dart';
import 'package:uuid/uuid.dart';
+import 'extensions_static.dart';
-final Logger _log = Logger("flutterPhoneState");
+final Logger _log = Logger('flutterPhoneState');
/// Represents phone events that surface from the device. These events can be subscribed to by
/// using [FlutterPhoneState.rawEventStream]
@@ -15,10 +15,10 @@ class RawPhoneEvent {
/// android: always null
/// ios: a uuid
/// others: ??
- final String id;
+ final String? id;
/// If available, the phone number being dialed.
- final String phoneNumber;
+ final String? phoneNumber;
/// The type of call event.
final RawEventType type;
@@ -26,8 +26,7 @@ class RawPhoneEvent {
RawPhoneEvent(this.id, this.phoneNumber, this.type);
/// Whether this event represents a new call
- bool get isNewCall =>
- type == RawEventType.inbound || type == RawEventType.outbound;
+ bool get isNewCall => type == RawEventType.inbound || type == RawEventType.outbound;
@override
String toString() {
@@ -61,15 +60,14 @@ class PhoneCallEvent {
/// @non_null
final DateTime timestamp;
- PhoneCallEvent(this.call, this.status, [DateTime eventDate])
- : timestamp = eventDate ?? DateTime.now();
+ PhoneCallEvent(this.call, this.status, [DateTime? eventDate]) : timestamp = eventDate ?? DateTime.now();
@override
String toString() {
return 'PhoneCallEvent{status: ${value(status)}, '
- 'id: ${truncate(call?.id, 12)} '
- 'callId: ${truncate(call?.callId, 12) ?? '-'}, '
- 'phoneNumber: ${call?.phoneNumber ?? '-'}}';
+ 'id: ${truncate(call.id, 12)} '
+ 'callId: ${truncate(call.callId, 12) ?? '-'}, '
+ 'phoneNumber: ${call.phoneNumber ?? '-'}}';
}
}
@@ -82,38 +80,37 @@ class PhoneCall {
/// An id assigned to the call by the underlying os
/// @nullable
- String callId;
+ String? callId;
/// The phone number being dialed, or the inbound number
/// @nullabe
- String phoneNumber;
+ String? phoneNumber;
/// The current status of the call
/// @non_null
- PhoneCallStatus status;
+ PhoneCallStatus status = PhoneCallStatus.disconnected;
/// Whether the call is inbound or outbound
/// @non_null
- final PhoneCallPlacement placement;
+ late final PhoneCallPlacement placement;
/// When the call was started
- final DateTime startTime;
+ late final DateTime startTime;
/// A list of events associated with this call
- final List events;
+ late final List events;
/// Whether or not this call is complete. see [isComplete]
bool _isComplete = false;
/// Used internally to track the call events, can be subscribed to, or awaited on.
- StreamController _eventStream;
+ StreamController? _eventStream;
/// The final call duration. See [duration]
- Duration _duration;
+ Duration? _duration;
- PhoneCall.start(this.phoneNumber, this.placement, [String id])
- : status = null,
- id = id ?? Uuid().v4(),
+ PhoneCall.start(this.phoneNumber, this.placement, [String? id])
+ : id = id ?? Uuid().v4(),
events = [],
startTime = DateTime.now();
@@ -126,18 +123,21 @@ class PhoneCall {
/// Marks this call as complete, and returns the final event as a [FutureOr]. If the
/// event stream has subscribers, it will first close, and then return
- Future complete(PhoneCallStatus status) async {
+ Future complete(PhoneCallStatus status) async {
if (_isComplete) {
- throw "Illegal state: This call is already marked complete";
+ throw 'Illegal state: This call is already marked complete';
}
- this._duration = DateTime.now().difference(startTime);
+ _duration = DateTime.now().difference(startTime);
final event = recordStatus(status);
_isComplete = true;
- if (_eventStream?.isClosed == false) {
- await _eventStream.close();
- return event;
- } else {
- return event;
+ final stream = _eventStream;
+ if (stream != null) {
+ if (stream.isClosed == false) {
+ await stream.close();
+ return event;
+ } else {
+ return event;
+ }
}
}
@@ -156,7 +156,7 @@ class PhoneCall {
FutureOr get done {
if (_isComplete) return this;
return _getOrCreateEventController().done.then((_) {
- _log.info("Finished call. Status $status");
+ _log.info('Finished call. Status $status');
return this;
});
}
@@ -165,10 +165,8 @@ class PhoneCall {
/// - It's in a dialing state for more than 30 seconds
/// - It's in an active state for more than 8 hours
bool get isExpired {
- if (status == PhoneCallStatus.dialing && sinceNow(startTime).inSeconds > 30)
- return true;
- if (status == PhoneCallStatus.connected && sinceNow(startTime).inHours > 8)
- return true;
+ if (status == PhoneCallStatus.dialing && sinceNow(startTime).inSeconds > 30) return true;
+ if (status == PhoneCallStatus.connected && sinceNow(startTime).inHours > 8) return true;
return false;
}
@@ -178,10 +176,8 @@ class PhoneCall {
/// Whether this call can be linked to the provided event. This check is fairly loose, it makes sure that
/// the values aren't for two disparate ids, phone numbers, and that the status is a subsequent status
bool canBeLinked(RawPhoneEvent event) {
- if (event.phoneNumber != null &&
- this.phoneNumber != null &&
- event.phoneNumber != this.phoneNumber) return false;
- if (this.callId != null && this.callId != event.id) return false;
+ if (event.phoneNumber != null && phoneNumber != null && event.phoneNumber != phoneNumber) return false;
+ if (callId != null && callId != event.id) return false;
if (isNotBefore(status, event.type)) return false;
return true;
@@ -189,8 +185,7 @@ class PhoneCall {
@override
bool operator ==(Object other) =>
- identical(this, other) ||
- other is PhoneCall && runtimeType == other.runtimeType && id == other.id;
+ identical(this, other) || other is PhoneCall && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
@@ -199,9 +194,10 @@ class PhoneCall {
PhoneCallEvent recordStatus(PhoneCallStatus status) {
this.status = status;
final event = PhoneCallEvent(this, status);
- this.events.add(event);
+ events.add(event);
+
if (_eventStream?.isClosed == true) {
- throw "Illegal state for call ${truncate(id, 12)}: Received status event after closing stream";
+ throw 'Illegal state for call ${truncate(id, 12)}: Received status event after closing stream';
}
_eventStream?.add(event);
return event;
@@ -212,25 +208,12 @@ class PhoneCall {
}
enum RawEventType { inbound, outbound, connected, disconnected }
-enum PhoneCallStatus {
- ringing,
- dialing,
- cancelled,
- error,
- connecting,
- connected,
- timedOut,
- disconnected
-}
+enum PhoneCallStatus { ringing, dialing, cancelled, error, connecting, connected, timedOut, disconnected }
enum PhoneCallPlacement { inbound, outbound }
const Map> priorStatuses = {
RawEventType.outbound: {PhoneCallStatus.dialing},
- RawEventType.connected: {
- PhoneCallStatus.connecting,
- PhoneCallStatus.ringing,
- PhoneCallStatus.dialing
- },
+ RawEventType.connected: {PhoneCallStatus.connecting, PhoneCallStatus.ringing, PhoneCallStatus.dialing},
RawEventType.inbound: {},
RawEventType.disconnected: {
PhoneCallStatus.connecting,
@@ -240,8 +223,7 @@ const Map> priorStatuses = {
},
};
-bool isNotBefore(PhoneCallStatus status, RawEventType type) =>
- !isBefore(status, type);
+bool isNotBefore(PhoneCallStatus status, RawEventType type) => !isBefore(status, type);
bool isBefore(PhoneCallStatus status, RawEventType type) {
return priorStatuses[type]?.contains(status) == true;
diff --git a/pubspec.lock b/pubspec.lock
index 06d0f90..2565649 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,63 +7,56 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.5.0-nullsafety.1"
+ version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.3"
+ version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.15.0-nullsafety.3"
- convert:
- dependency: transitive
- description:
- name: convert
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.1"
+ version: "1.15.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.3"
+ version: "3.0.1"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
@@ -79,55 +72,55 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.6.3"
logging:
dependency: "direct main"
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
- version: "0.11.4"
+ version: "1.0.1"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.10-nullsafety.1"
+ version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.1"
- platform_detect:
- dependency: transitive
+ version: "1.8.0"
+ pedantic:
+ dependency: "direct dev"
description:
- name: platform_detect
+ name: pedantic
url: "https://pub.dartlang.org"
source: hosted
- version: "1.4.0"
+ version: "1.11.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.2"
- pub_semver:
- dependency: transitive
- description:
- name: pub_semver
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.4.4"
+ version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
@@ -139,105 +132,112 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.2"
+ version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
- version: "1.10.0-nullsafety.1"
+ version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
stream_transform:
dependency: "direct main"
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.19-nullsafety.2"
+ version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
- version: "5.5.0"
+ version: "6.0.6"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
- version: "0.0.1+1"
+ version: "2.0.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
- version: "0.0.1+7"
+ version: "2.0.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.7"
+ version: "2.0.3"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
- version: "0.1.2"
+ version: "2.0.0"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
uuid:
dependency: "direct main"
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.4"
+ version: "3.0.4"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.3"
+ version: "2.1.0"
sdks:
- dart: ">=2.10.0-110 <2.11.0"
- flutter: ">=1.12.13+hotfix.5 <2.0.0"
+ dart: ">=2.12.0 <3.0.0"
+ flutter: ">=2.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 3740f38..10c2b4b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,24 +1,25 @@
name: flutter_phone_state
description: This plugin provides an easy way to make phone calls, and track the state of the phone call
-version: 0.5.9
+version: 2.0.0
homepage: "https://github.com/SunnyApp/flutter_phone_state.git"
environment:
- sdk: ">=2.5.0 <3.0.0"
+ sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
- url_launcher: ^5.4.2
- logging: ^0.11.4
- uuid: ^2.0.4
- stream_transform: ^1.1.0
+ logging: ^1.0.1
+ stream_transform: ^2.0.0
+ url_launcher: ^6.0.6
+ uuid: ^3.0.4
dev_dependencies:
flutter_test:
sdk: flutter
+ pedantic: ^1.11.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
diff --git a/test/flutter_phone_state_test.dart b/test/flutter_phone_state_test.dart
index 2a1ee5b..65b6996 100644
--- a/test/flutter_phone_state_test.dart
+++ b/test/flutter_phone_state_test.dart
@@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_phone_state/flutter_phone_state.dart';
void main() {
- const MethodChannel channel = MethodChannel('flutter_phone_state');
+ const channel = MethodChannel('flutter_phone_state');
TestWidgetsFlutterBinding.ensureInitialized();