You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
703 lines
23 KiB
703 lines
23 KiB
2 years ago
|
import 'dart:async';
|
||
|
import 'dart:ui';
|
||
|
|
||
|
import 'package:flutter/services.dart';
|
||
|
import 'package:flutter/widgets.dart';
|
||
|
import 'package:platform/platform.dart';
|
||
|
|
||
|
part 'constants.dart';
|
||
|
|
||
|
part 'filter.dart';
|
||
|
|
||
|
typedef MessageHandler(SmsMessage message);
|
||
|
typedef SmsSendStatusListener(SendStatus status);
|
||
|
|
||
|
void _flutterSmsSetupBackgroundChannel(
|
||
|
{MethodChannel backgroundChannel =
|
||
|
const MethodChannel(_BACKGROUND_CHANNEL)}) async {
|
||
|
WidgetsFlutterBinding.ensureInitialized();
|
||
|
|
||
|
backgroundChannel.setMethodCallHandler((call) async {
|
||
|
if (call.method == HANDLE_BACKGROUND_MESSAGE) {
|
||
|
final CallbackHandle handle =
|
||
|
CallbackHandle.fromRawHandle(call.arguments['handle']);
|
||
|
final Function handlerFunction =
|
||
|
PluginUtilities.getCallbackFromHandle(handle)!;
|
||
|
try {
|
||
|
await handlerFunction(SmsMessage.fromMap(
|
||
|
call.arguments['message'], INCOMING_SMS_COLUMNS));
|
||
|
} catch (e) {
|
||
|
print('Unable to handle incoming background message.');
|
||
|
print(e);
|
||
|
}
|
||
|
return Future<void>.value();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
backgroundChannel.invokeMethod<void>(BACKGROUND_SERVICE_INITIALIZED);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// A Flutter plugin to use telephony features such as
|
||
|
/// - Send SMS Messages
|
||
|
/// - Query SMS Messages
|
||
|
/// - Listen for incoming SMS
|
||
|
/// - Retrieve various network parameters
|
||
|
///
|
||
|
///
|
||
|
/// This plugin tries to replicate some of the functionality provided by Android's Telephony class.
|
||
|
///
|
||
|
///
|
||
|
class Telephony {
|
||
|
final MethodChannel _foregroundChannel;
|
||
|
final Platform _platform;
|
||
|
|
||
|
late MessageHandler _onNewMessage;
|
||
|
late MessageHandler _onBackgroundMessages;
|
||
|
late SmsSendStatusListener _statusListener;
|
||
|
|
||
|
///
|
||
|
/// Gets a singleton instance of the [Telephony] class.
|
||
|
///
|
||
|
static Telephony get instance => _instance;
|
||
|
|
||
|
///
|
||
|
/// Gets a singleton instance of the [Telephony] class to be used in background execution context.
|
||
|
///
|
||
|
static Telephony get backgroundInstance => _backgroundInstance;
|
||
|
|
||
|
/// ## Do not call this method. This method is visible only for testing.
|
||
|
@visibleForTesting
|
||
|
Telephony.private(MethodChannel methodChannel, Platform platform)
|
||
|
: _foregroundChannel = methodChannel,
|
||
|
_platform = platform;
|
||
|
|
||
|
Telephony._newInstance(MethodChannel methodChannel, LocalPlatform platform)
|
||
|
: _foregroundChannel = methodChannel,
|
||
|
_platform = platform {
|
||
|
_foregroundChannel.setMethodCallHandler(handler);
|
||
|
}
|
||
|
|
||
|
static final Telephony _instance = Telephony._newInstance(
|
||
|
const MethodChannel(_FOREGROUND_CHANNEL), const LocalPlatform());
|
||
|
static final Telephony _backgroundInstance = Telephony._newInstance(
|
||
|
const MethodChannel(_FOREGROUND_CHANNEL), const LocalPlatform());
|
||
|
|
||
|
///
|
||
|
/// Listens to incoming SMS.
|
||
|
///
|
||
|
/// ### Requires RECEIVE_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [onNewMessage] : Called on every new message received when app is in foreground.
|
||
|
/// - [onBackgroundMessage] (optional) : Called on every new message received when app is in background.
|
||
|
/// - [listenInBackground] (optional) : Defaults to true. Set to false to only listen to messages in foreground. [listenInBackground] is
|
||
|
/// ignored if [onBackgroundMessage] is not set.
|
||
|
///
|
||
|
///
|
||
|
void listenIncomingSms(
|
||
|
{required MessageHandler onNewMessage,
|
||
|
MessageHandler? onBackgroundMessage,
|
||
|
bool listenInBackground = true}) {
|
||
|
assert(_platform.isAndroid == true, "Can only be called on Android.");
|
||
|
assert(
|
||
|
listenInBackground
|
||
|
? onBackgroundMessage != null
|
||
|
: onBackgroundMessage == null,
|
||
|
listenInBackground
|
||
|
? "`onBackgroundMessage` cannot be null when `listenInBackground` is true. Set `listenInBackground` to false if you don't need background processing."
|
||
|
: "You have set `listenInBackground` to false. `onBackgroundMessage` can only be set when `listenInBackground` is true");
|
||
|
|
||
|
_onNewMessage = onNewMessage;
|
||
|
|
||
|
if (listenInBackground && onBackgroundMessage != null) {
|
||
|
_onBackgroundMessages = onBackgroundMessage;
|
||
|
final CallbackHandle backgroundSetupHandle =
|
||
|
PluginUtilities.getCallbackHandle(_flutterSmsSetupBackgroundChannel)!;
|
||
|
final CallbackHandle? backgroundMessageHandle =
|
||
|
PluginUtilities.getCallbackHandle(_onBackgroundMessages);
|
||
|
|
||
|
if (backgroundMessageHandle == null) {
|
||
|
throw ArgumentError(
|
||
|
'''Failed to setup background message handler! `onBackgroundMessage`
|
||
|
should be a TOP-LEVEL OR STATIC FUNCTION and should NOT be tied to a
|
||
|
class or an anonymous function.''',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_foregroundChannel.invokeMethod<bool>(
|
||
|
'startBackgroundService',
|
||
|
<String, dynamic>{
|
||
|
'setupHandle': backgroundSetupHandle.toRawHandle(),
|
||
|
'backgroundHandle': backgroundMessageHandle.toRawHandle()
|
||
|
},
|
||
|
);
|
||
|
} else {
|
||
|
_foregroundChannel.invokeMethod('disableBackgroundService');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// ## Do not call this method. This method is visible only for testing.
|
||
|
@visibleForTesting
|
||
|
Future<dynamic> handler(MethodCall call) async {
|
||
|
switch (call.method) {
|
||
|
case ON_MESSAGE:
|
||
|
final message = call.arguments["message"];
|
||
|
return _onNewMessage(SmsMessage.fromMap(message, INCOMING_SMS_COLUMNS));
|
||
|
case SMS_SENT:
|
||
|
return _statusListener(SendStatus.SENT);
|
||
|
case SMS_DELIVERED:
|
||
|
return _statusListener(SendStatus.DELIVERED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Query SMS Inbox.
|
||
|
///
|
||
|
/// ### Requires READ_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [columns] (optional) : List of [SmsColumn] to be returned by this query. Defaults to [ SmsColumn.ID, SmsColumn.ADDRESS, SmsColumn.BODY, SmsColumn.DATE ]
|
||
|
/// - [filter] (optional) : [SmsFilter] to filter the results of this query. Works like SQL WHERE clause.
|
||
|
/// - [sortOrder] (optional): List of [OrderBy]. Orders the results of this query by the provided columns and order.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<List<SmsMessage>>]
|
||
|
Future<List<SmsMessage>> getInboxSms(
|
||
|
{List<SmsColumn> columns = DEFAULT_SMS_COLUMNS,
|
||
|
SmsFilter? filter,
|
||
|
List<OrderBy>? sortOrder}) async {
|
||
|
assert(_platform.isAndroid == true, "Can only be called on Android.");
|
||
|
final args = _getArguments(columns, filter, sortOrder);
|
||
|
|
||
|
final messages =
|
||
|
await _foregroundChannel.invokeMethod<List?>(GET_ALL_INBOX_SMS, args);
|
||
|
|
||
|
return messages
|
||
|
?.map((message) => SmsMessage.fromMap(message, columns))
|
||
|
.toList(growable: false) ??
|
||
|
List.empty();
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Query SMS Outbox / Sent messages.
|
||
|
///
|
||
|
/// ### Requires READ_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [columns] (optional) : List of [SmsColumn] to be returned by this query. Defaults to [ SmsColumn.ID, SmsColumn.ADDRESS, SmsColumn.BODY, SmsColumn.DATE ]
|
||
|
/// - [filter] (optional) : [SmsFilter] to filter the results of this query. Works like SQL WHERE clause.
|
||
|
/// - [sortOrder] (optional): List of [OrderBy]. Orders the results of this query by the provided columns and order.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<List<SmsMessage>>]
|
||
|
Future<List<SmsMessage>> getSentSms(
|
||
|
{List<SmsColumn> columns = DEFAULT_SMS_COLUMNS,
|
||
|
SmsFilter? filter,
|
||
|
List<OrderBy>? sortOrder}) async {
|
||
|
assert(_platform.isAndroid == true, "Can only be called on Android.");
|
||
|
final args = _getArguments(columns, filter, sortOrder);
|
||
|
|
||
|
final messages =
|
||
|
await _foregroundChannel.invokeMethod<List?>(GET_ALL_SENT_SMS, args);
|
||
|
|
||
|
return messages
|
||
|
?.map((message) => SmsMessage.fromMap(message, columns))
|
||
|
.toList(growable: false) ??
|
||
|
List.empty();
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Query SMS Drafts.
|
||
|
///
|
||
|
/// ### Requires READ_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [columns] (optional) : List of [SmsColumn] to be returned by this query. Defaults to [ SmsColumn.ID, SmsColumn.ADDRESS, SmsColumn.BODY, SmsColumn.DATE ]
|
||
|
/// - [filter] (optional) : [SmsFilter] to filter the results of this query. Works like SQL WHERE clause.
|
||
|
/// - [sortOrder] (optional): List of [OrderBy]. Orders the results of this query by the provided columns and order.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<List<SmsMessage>>]
|
||
|
Future<List<SmsMessage>> getDraftSms(
|
||
|
{List<SmsColumn> columns = DEFAULT_SMS_COLUMNS,
|
||
|
SmsFilter? filter,
|
||
|
List<OrderBy>? sortOrder}) async {
|
||
|
assert(_platform.isAndroid == true, "Can only be called on Android.");
|
||
|
final args = _getArguments(columns, filter, sortOrder);
|
||
|
|
||
|
final messages =
|
||
|
await _foregroundChannel.invokeMethod<List?>(GET_ALL_DRAFT_SMS, args);
|
||
|
|
||
|
return messages
|
||
|
?.map((message) => SmsMessage.fromMap(message, columns))
|
||
|
.toList(growable: false) ??
|
||
|
List.empty();
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Query SMS Inbox.
|
||
|
///
|
||
|
/// ### Requires READ_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [filter] (optional) : [ConversationFilter] to filter the results of this query. Works like SQL WHERE clause.
|
||
|
/// - [sortOrder] (optional): List of [OrderBy]. Orders the results of this query by the provided columns and order.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<List<SmsConversation>>]
|
||
|
Future<List<SmsConversation>> getConversations(
|
||
|
{ConversationFilter? filter, List<OrderBy>? sortOrder}) async {
|
||
|
assert(_platform.isAndroid == true, "Can only be called on Android.");
|
||
|
final args = _getArguments(DEFAULT_CONVERSATION_COLUMNS, filter, sortOrder);
|
||
|
|
||
|
final conversations = await _foregroundChannel.invokeMethod<List?>(
|
||
|
GET_ALL_CONVERSATIONS, args);
|
||
|
|
||
|
return conversations
|
||
|
?.map((conversation) => SmsConversation.fromMap(conversation))
|
||
|
.toList(growable: false) ??
|
||
|
List.empty();
|
||
|
}
|
||
|
|
||
|
Map<String, dynamic> _getArguments(List<_TelephonyColumn> columns,
|
||
|
Filter? filter, List<OrderBy>? sortOrder) {
|
||
|
final Map<String, dynamic> args = {};
|
||
|
|
||
|
args["projection"] = columns.map((c) => c._name).toList();
|
||
|
|
||
|
if (filter != null) {
|
||
|
args["selection"] = filter.selection;
|
||
|
args["selection_args"] = filter.selectionArgs;
|
||
|
}
|
||
|
|
||
|
if (sortOrder != null && sortOrder.isNotEmpty) {
|
||
|
args["sort_order"] = sortOrder.map((o) => o._value).join(",");
|
||
|
}
|
||
|
|
||
|
return args;
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Send an SMS directly from your application. Uses Android's SmsManager to send SMS.
|
||
|
///
|
||
|
/// ### Requires SEND_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [to] : Address to send the SMS to.
|
||
|
/// - [message] : Message to be sent. If message body is longer than standard SMS length limits set appropriate
|
||
|
/// value for [isMultipart]
|
||
|
/// - [statusListener] (optional) : Listen to the status of the sent SMS. Values can be one of [SmsStatus]
|
||
|
/// - [isMultipart] (optional) : If message body is longer than standard SMS limit of 160 characters, set this flag to
|
||
|
/// send the SMS in multiple parts.
|
||
|
Future<void> sendSms({
|
||
|
required String to,
|
||
|
required String message,
|
||
|
SmsSendStatusListener? statusListener,
|
||
|
bool isMultipart = false,
|
||
|
}) async {
|
||
|
assert(_platform.isAndroid == true, "Can only be called on Android.");
|
||
|
bool listenStatus = false;
|
||
|
if (statusListener != null) {
|
||
|
_statusListener = statusListener;
|
||
|
listenStatus = true;
|
||
|
}
|
||
|
final Map<String, dynamic> args = {
|
||
|
"address": to,
|
||
|
"message_body": message,
|
||
|
"listen_status": listenStatus
|
||
|
};
|
||
|
final String method = isMultipart ? SEND_MULTIPART_SMS : SEND_SMS;
|
||
|
await _foregroundChannel.invokeMethod(method, args);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Open Android's default SMS application with the provided message and address.
|
||
|
///
|
||
|
/// ### Requires SEND_SMS permission.
|
||
|
///
|
||
|
/// Parameters:
|
||
|
///
|
||
|
/// - [to] : Address to send the SMS to.
|
||
|
/// - [message] : Message to be sent.
|
||
|
///
|
||
|
Future<void> sendSmsByDefaultApp({
|
||
|
required String to,
|
||
|
required String message,
|
||
|
}) async {
|
||
|
final Map<String, dynamic> args = {
|
||
|
"address": to,
|
||
|
"message_body": message,
|
||
|
};
|
||
|
await _foregroundChannel.invokeMethod(SEND_SMS_INTENT, args);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Checks if the device has necessary features to send and receive SMS.
|
||
|
///
|
||
|
/// Uses TelephonyManager class on Android.
|
||
|
///
|
||
|
Future<bool?> get isSmsCapable =>
|
||
|
_foregroundChannel.invokeMethod<bool>(IS_SMS_CAPABLE);
|
||
|
|
||
|
///
|
||
|
/// Returns a constant indicating the current data connection state (cellular).
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<DataState>]
|
||
|
Future<DataState> get cellularDataState async {
|
||
|
final int? dataState =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_CELLULAR_DATA_STATE);
|
||
|
if (dataState == null || dataState == -1) {
|
||
|
return DataState.UNKNOWN;
|
||
|
} else {
|
||
|
return DataState.values[dataState];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns a constant that represents the current state of all phone calls.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<CallState>]
|
||
|
Future<CallState> get callState async {
|
||
|
final int? state =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_CALL_STATE);
|
||
|
if (state != null) {
|
||
|
return CallState.values[state];
|
||
|
} else {
|
||
|
return CallState.UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns a constant that represents the current state of all phone calls.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<CallState>]
|
||
|
Future<DataActivity> get dataActivity async {
|
||
|
final int? activity =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_DATA_ACTIVITY);
|
||
|
if (activity != null) {
|
||
|
return DataActivity.values[activity];
|
||
|
} else {
|
||
|
return DataActivity.UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns the numeric name (MCC+MNC) of current registered operator.
|
||
|
///
|
||
|
/// Availability: Only when user is registered to a network.
|
||
|
///
|
||
|
/// Result may be unreliable on CDMA networks (use phoneType to determine if on a CDMA network).
|
||
|
///
|
||
|
Future<String?> get networkOperator =>
|
||
|
_foregroundChannel.invokeMethod<String>(GET_NETWORK_OPERATOR);
|
||
|
|
||
|
///
|
||
|
/// Returns the alphabetic name of current registered operator.
|
||
|
///
|
||
|
/// Availability: Only when user is registered to a network.
|
||
|
///
|
||
|
/// Result may be unreliable on CDMA networks (use phoneType to determine if on a CDMA network).
|
||
|
///
|
||
|
Future<String?> get networkOperatorName =>
|
||
|
_foregroundChannel.invokeMethod<String>(GET_NETWORK_OPERATOR_NAME);
|
||
|
|
||
|
///
|
||
|
/// Returns a constant indicating the radio technology (network type) currently in use on the device for data transmission.
|
||
|
///
|
||
|
/// ### Requires READ_PHONE_STATE permission.
|
||
|
///
|
||
|
Future<NetworkType> get dataNetworkType async {
|
||
|
final int? type =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_DATA_NETWORK_TYPE);
|
||
|
if (type != null) {
|
||
|
return NetworkType.values[type];
|
||
|
} else {
|
||
|
return NetworkType.UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns a constant indicating the device phone type. This indicates the type of radio used to transmit voice calls.
|
||
|
///
|
||
|
Future<PhoneType> get phoneType async {
|
||
|
final int? type =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_PHONE_TYPE);
|
||
|
if (type != null) {
|
||
|
return PhoneType.values[type];
|
||
|
} else {
|
||
|
return PhoneType.UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns the MCC+MNC (mobile country code + mobile network code) of the provider of the SIM. 5 or 6 decimal digits.
|
||
|
///
|
||
|
/// Availability: SimState must be SIM\_STATE\_READY
|
||
|
Future<String?> get simOperator =>
|
||
|
_foregroundChannel.invokeMethod<String>(GET_SIM_OPERATOR);
|
||
|
|
||
|
///
|
||
|
/// Returns the Service Provider Name (SPN).
|
||
|
///
|
||
|
/// Availability: SimState must be SIM_STATE_READY
|
||
|
Future<String?> get simOperatorName =>
|
||
|
_foregroundChannel.invokeMethod<String>(GET_SIM_OPERATOR_NAME);
|
||
|
|
||
|
///
|
||
|
/// Returns a constant indicating the state of the default SIM card.
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<SimState>]
|
||
|
Future<SimState> get simState async {
|
||
|
final int? state =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_SIM_STATE);
|
||
|
if (state != null) {
|
||
|
return SimState.values[state];
|
||
|
} else {
|
||
|
return SimState.UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns true if the device is considered roaming on the current network, for GSM purposes.
|
||
|
///
|
||
|
/// Availability: Only when user registered to a network.
|
||
|
Future<bool?> get isNetworkRoaming =>
|
||
|
_foregroundChannel.invokeMethod<bool>(IS_NETWORK_ROAMING);
|
||
|
|
||
|
///
|
||
|
/// Returns a List of SignalStrength or an empty List if there are no valid measurements.
|
||
|
///
|
||
|
/// ### Requires Android build version 29 --> Android Q
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<List<SignalStrength>>]
|
||
|
Future<List<SignalStrength>> get signalStrengths async {
|
||
|
final List<dynamic>? strengths =
|
||
|
await _foregroundChannel.invokeMethod(GET_SIGNAL_STRENGTH);
|
||
|
return (strengths ?? [])
|
||
|
.map((s) => SignalStrength.values[s])
|
||
|
.toList(growable: false);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Returns current voice service state.
|
||
|
///
|
||
|
/// ### Requires Android build version 26 --> Android O
|
||
|
/// ### Requires permissions ACCESS_COARSE_LOCATION and READ_PHONE_STATE
|
||
|
///
|
||
|
/// Returns:
|
||
|
///
|
||
|
/// [Future<ServiceState>]
|
||
|
Future<ServiceState> get serviceState async {
|
||
|
final int? state =
|
||
|
await _foregroundChannel.invokeMethod<int>(GET_SERVICE_STATE);
|
||
|
if (state != null) {
|
||
|
return ServiceState.values[state];
|
||
|
} else {
|
||
|
return ServiceState.UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Request the user for all the sms permissions listed in the app's AndroidManifest.xml
|
||
|
///
|
||
|
Future<bool?> get requestSmsPermissions =>
|
||
|
_foregroundChannel.invokeMethod<bool>(REQUEST_SMS_PERMISSION);
|
||
|
|
||
|
///
|
||
|
/// Request the user for all the phone permissions listed in the app's AndroidManifest.xml
|
||
|
///
|
||
|
Future<bool?> get requestPhonePermissions =>
|
||
|
_foregroundChannel.invokeMethod<bool>(REQUEST_PHONE_PERMISSION);
|
||
|
|
||
|
///
|
||
|
/// Request the user for all the phone and sms permissions listed in the app's AndroidManifest.xml
|
||
|
///
|
||
|
Future<bool?> get requestPhoneAndSmsPermissions =>
|
||
|
_foregroundChannel.invokeMethod<bool>(REQUEST_PHONE_AND_SMS_PERMISSION);
|
||
|
|
||
|
///
|
||
|
/// Opens the default dialer with the given phone number.
|
||
|
///
|
||
|
Future<void> openDialer(String phoneNumber) async {
|
||
|
assert(phoneNumber.isNotEmpty, "phoneNumber cannot be empty");
|
||
|
final Map<String, dynamic> args = {"phoneNumber": phoneNumber};
|
||
|
await _foregroundChannel.invokeMethod(OPEN_DIALER, args);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Starts a phone all with the given phone number.
|
||
|
///
|
||
|
/// ### Requires permission CALL_PHONE
|
||
|
///
|
||
|
Future<void> dialPhoneNumber(String phoneNumber) async {
|
||
|
assert(phoneNumber.isNotEmpty, "phoneNumber cannot be null or empty");
|
||
|
final Map<String, dynamic> args = {"phoneNumber": phoneNumber};
|
||
|
await _foregroundChannel.invokeMethod(DIAL_PHONE_NUMBER, args);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Represents a message returned by one of the query functions such as
|
||
|
/// [getInboxSms], [getSentSms], [getDraftSms]
|
||
|
class SmsMessage {
|
||
|
int? id;
|
||
|
String? address;
|
||
|
String? body;
|
||
|
int? date;
|
||
|
int? dateSent;
|
||
|
bool? read;
|
||
|
bool? seen;
|
||
|
String? subject;
|
||
|
int? subscriptionId;
|
||
|
int? threadId;
|
||
|
SmsType? type;
|
||
|
SmsStatus? status;
|
||
|
String? serviceCenterAddress;
|
||
|
|
||
|
/// ## Do not call this method. This method is visible only for testing.
|
||
|
@visibleForTesting
|
||
|
SmsMessage.fromMap(Map rawMessage, List<SmsColumn> columns) {
|
||
|
final message = Map.castFrom<dynamic, dynamic, String, dynamic>(rawMessage);
|
||
|
for (var column in columns) {
|
||
|
debugPrint('Column is ${column._columnName}');
|
||
|
final value = message[column._columnName];
|
||
|
switch (column._columnName) {
|
||
|
case _SmsProjections.ID:
|
||
|
this.id = int.tryParse(value);
|
||
|
break;
|
||
|
case _SmsProjections.ORIGINATING_ADDRESS:
|
||
|
case _SmsProjections.ADDRESS:
|
||
|
this.address = value;
|
||
|
break;
|
||
|
case _SmsProjections.MESSAGE_BODY:
|
||
|
case _SmsProjections.BODY:
|
||
|
this.body = value;
|
||
|
break;
|
||
|
case _SmsProjections.DATE:
|
||
|
case _SmsProjections.TIMESTAMP:
|
||
|
this.date = int.tryParse(value);
|
||
|
break;
|
||
|
case _SmsProjections.DATE_SENT:
|
||
|
this.dateSent = int.tryParse(value);
|
||
|
break;
|
||
|
case _SmsProjections.READ:
|
||
|
this.read = int.tryParse(value) == 0 ? false : true;
|
||
|
break;
|
||
|
case _SmsProjections.SEEN:
|
||
|
this.seen = int.tryParse(value) == 0 ? false : true;
|
||
|
break;
|
||
|
case _SmsProjections.STATUS:
|
||
|
switch (int.tryParse(value)) {
|
||
|
case 0:
|
||
|
this.status = SmsStatus.STATUS_COMPLETE;
|
||
|
break;
|
||
|
case 32:
|
||
|
this.status = SmsStatus.STATUS_PENDING;
|
||
|
break;
|
||
|
case 64:
|
||
|
this.status = SmsStatus.STATUS_FAILED;
|
||
|
break;
|
||
|
case -1:
|
||
|
default:
|
||
|
this.status = SmsStatus.STATUS_NONE;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case _SmsProjections.SUBJECT:
|
||
|
this.subject = value;
|
||
|
break;
|
||
|
case _SmsProjections.SUBSCRIPTION_ID:
|
||
|
this.subscriptionId = int.tryParse(value);
|
||
|
break;
|
||
|
case _SmsProjections.THREAD_ID:
|
||
|
this.threadId = int.tryParse(value);
|
||
|
break;
|
||
|
case _SmsProjections.TYPE:
|
||
|
var smsTypeIndex = int.tryParse(value);
|
||
|
this.type =
|
||
|
smsTypeIndex != null ? SmsType.values[smsTypeIndex] : null;
|
||
|
break;
|
||
|
case _SmsProjections.SERVICE_CENTER_ADDRESS:
|
||
|
this.serviceCenterAddress = value;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// ## Do not call this method. This method is visible only for testing.
|
||
|
@visibleForTesting
|
||
|
bool equals(SmsMessage other) {
|
||
|
return this.id == other.id &&
|
||
|
this.address == other.address &&
|
||
|
this.body == other.body &&
|
||
|
this.date == other.date &&
|
||
|
this.dateSent == other.dateSent &&
|
||
|
this.read == other.read &&
|
||
|
this.seen == other.seen &&
|
||
|
this.subject == other.subject &&
|
||
|
this.subscriptionId == other.subscriptionId &&
|
||
|
this.threadId == other.threadId &&
|
||
|
this.type == other.type &&
|
||
|
this.status == other.status;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Represents a conversation returned by the query conversation functions
|
||
|
/// [getConversations]
|
||
|
class SmsConversation {
|
||
|
String? snippet;
|
||
|
int? threadId;
|
||
|
int? messageCount;
|
||
|
|
||
|
/// ## Do not call this method. This method is visible only for testing.
|
||
|
@visibleForTesting
|
||
|
SmsConversation.fromMap(Map rawConversation) {
|
||
|
final conversation =
|
||
|
Map.castFrom<dynamic, dynamic, String, dynamic>(rawConversation);
|
||
|
for (var column in DEFAULT_CONVERSATION_COLUMNS) {
|
||
|
final String? value = conversation[column._columnName];
|
||
|
switch (column._columnName) {
|
||
|
case _ConversationProjections.SNIPPET:
|
||
|
this.snippet = value;
|
||
|
break;
|
||
|
case _ConversationProjections.THREAD_ID:
|
||
|
this.threadId = int.tryParse(value!);
|
||
|
break;
|
||
|
case _ConversationProjections.MSG_COUNT:
|
||
|
this.messageCount = int.tryParse(value!);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// ## Do not call this method. This method is visible only for testing.
|
||
|
@visibleForTesting
|
||
|
bool equals(SmsConversation other) {
|
||
|
return this.threadId == other.threadId &&
|
||
|
this.snippet == other.snippet &&
|
||
|
this.messageCount == other.messageCount;
|
||
|
}
|
||
|
}
|