2019-10-26 04:42:50 +02:00
import ' dart:async ' ;
2020-05-11 02:48:41 +02:00
import ' dart:collection ' ;
2022-04-25 17:39:04 +02:00
import ' dart:typed_data ' ;
2019-10-26 04:42:50 +02:00
import ' package:flutter/services.dart ' ;
2021-03-01 03:21:07 +01:00
import ' package:flutter_inappwebview/src/util.dart ' ;
2019-10-26 04:42:50 +02:00
2022-04-20 02:18:36 +02:00
import ' chrome_safari_browser_settings.dart ' ;
2019-10-26 04:42:50 +02:00
2021-02-22 23:38:30 +01:00
class ChromeSafariBrowserAlreadyOpenedException implements Exception {
final dynamic message ;
ChromeSafariBrowserAlreadyOpenedException ( [ this . message ] ) ;
String toString ( ) {
Object ? message = this . message ;
if ( message = = null ) return " ChromeSafariBrowserAlreadyOpenedException " ;
return " ChromeSafariBrowserAlreadyOpenedException: $ message " ;
}
}
class ChromeSafariBrowserNotOpenedException implements Exception {
final dynamic message ;
ChromeSafariBrowserNotOpenedException ( [ this . message ] ) ;
String toString ( ) {
Object ? message = this . message ;
if ( message = = null ) return " ChromeSafariBrowserNotOpenedException " ;
return " ChromeSafariBrowserNotOpenedException: $ message " ;
}
}
2019-10-26 04:42:50 +02:00
///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android
///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
///
2021-02-22 12:16:23 +01:00
///**NOTE**: If you want to use the `ChromeSafariBrowser` class on Android 11+ you need to specify your app querying for
///`android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml`
///(you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above).
2022-04-25 22:36:21 +02:00
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
2019-10-26 04:42:50 +02:00
class ChromeSafariBrowser {
2021-03-19 17:34:32 +01:00
///View ID used internally.
late final String id ;
2022-04-25 17:39:04 +02:00
ChromeSafariBrowserActionButton ? _actionButton ;
2020-05-11 02:48:41 +02:00
Map < int , ChromeSafariBrowserMenuItem > _menuItems = new HashMap ( ) ;
2019-10-26 04:42:50 +02:00
bool _isOpened = false ;
2021-01-28 17:10:15 +01:00
late MethodChannel _channel ;
2020-05-29 19:56:03 +02:00
static const MethodChannel _sharedChannel =
const MethodChannel ( ' com.pichillilorenzo/flutter_chromesafaribrowser ' ) ;
2019-10-26 04:42:50 +02:00
2021-02-22 12:16:23 +01:00
ChromeSafariBrowser ( ) {
2021-03-11 22:42:18 +01:00
id = IdGenerator . generate ( ) ;
2019-12-18 21:34:40 +01:00
this . _channel =
2021-03-01 03:21:07 +01:00
MethodChannel ( ' com.pichillilorenzo/flutter_chromesafaribrowser_ $ id ' ) ;
2019-12-18 21:34:40 +01:00
this . _channel . setMethodCallHandler ( handleMethod ) ;
2019-10-26 04:42:50 +02:00
_isOpened = false ;
}
Future < dynamic > handleMethod ( MethodCall call ) async {
2019-12-01 12:55:06 +01:00
switch ( call . method ) {
2019-10-26 04:42:50 +02:00
case " onChromeSafariBrowserOpened " :
onOpened ( ) ;
break ;
2019-12-18 01:56:21 +01:00
case " onChromeSafariBrowserCompletedInitialLoad " :
onCompletedInitialLoad ( ) ;
2019-10-26 04:42:50 +02:00
break ;
case " onChromeSafariBrowserClosed " :
onClosed ( ) ;
this . _isOpened = false ;
break ;
2022-04-25 17:39:04 +02:00
case " onChromeSafariBrowserItemActionPerform " :
2020-05-11 02:48:41 +02:00
String url = call . arguments [ " url " ] ;
String title = call . arguments [ " title " ] ;
int id = call . arguments [ " id " ] . toInt ( ) ;
2022-04-25 17:39:04 +02:00
if ( this . _actionButton ? . id = = id ) {
this . _actionButton ? . action ( url , title ) ;
} else if ( this . _menuItems [ id ] ! = null ) {
this . _menuItems [ id ] ? . action ( url , title ) ;
2021-01-28 17:10:15 +01:00
}
2020-05-11 02:48:41 +02:00
break ;
2019-10-26 04:42:50 +02:00
default :
throw UnimplementedError ( " Unimplemented ${ call . method } method " ) ;
}
}
2021-03-23 21:53:42 +01:00
///Opens the [ChromeSafariBrowser] instance with an [url].
2019-10-26 04:42:50 +02:00
///
2021-02-22 12:16:23 +01:00
///[url]: The [url] to load.
2019-10-26 04:42:50 +02:00
///
2019-11-10 14:11:30 +01:00
///[options]: Options for the [ChromeSafariBrowser].
2022-04-20 02:18:36 +02:00
///
///[settings]: Settings for the [ChromeSafariBrowser].
2019-12-01 12:55:06 +01:00
Future < void > open (
2022-04-20 02:18:36 +02:00
{ required Uri url ,
2022-04-20 03:05:46 +02:00
@ Deprecated ( ' Use settings instead ' )
// ignore: deprecated_member_use_from_same_package
ChromeSafariBrowserClassOptions ? options ,
2022-04-20 02:18:36 +02:00
ChromeSafariBrowserSettings ? settings } ) async {
2021-02-22 12:16:23 +01:00
assert ( url . toString ( ) . isNotEmpty ) ;
2019-10-26 04:42:50 +02:00
this . throwIsAlreadyOpened ( message: ' Cannot open $ url ! ' ) ;
2019-10-27 04:35:05 +01:00
2021-01-28 17:10:15 +01:00
List < Map < String , dynamic > > menuItemList = [ ] ;
2020-05-11 02:48:41 +02:00
_menuItems . forEach ( ( key , value ) {
2022-04-25 17:39:04 +02:00
menuItemList . add ( value . toMap ( ) ) ;
2020-05-11 02:48:41 +02:00
} ) ;
2019-10-27 04:35:05 +01:00
2022-04-20 03:05:46 +02:00
var initialSettings = settings ? . toMap ( ) ? ?
options ? . toMap ( ) ? ?
2022-04-20 02:18:36 +02:00
ChromeSafariBrowserSettings ( ) . toMap ( ) ;
2019-10-26 04:42:50 +02:00
Map < String , dynamic > args = < String , dynamic > { } ;
2021-03-01 03:21:07 +01:00
args . putIfAbsent ( ' id ' , ( ) = > id ) ;
2021-02-22 12:16:23 +01:00
args . putIfAbsent ( ' url ' , ( ) = > url . toString ( ) ) ;
2022-04-20 02:18:36 +02:00
args . putIfAbsent ( ' settings ' , ( ) = > initialSettings ) ;
2022-04-25 17:39:04 +02:00
args . putIfAbsent ( ' actionButton ' , ( ) = > _actionButton ? . toMap ( ) ) ;
2020-05-21 03:34:39 +02:00
args . putIfAbsent ( ' menuItemList ' , ( ) = > menuItemList ) ;
2019-12-18 21:34:40 +01:00
await _sharedChannel . invokeMethod ( ' open ' , args ) ;
2019-10-26 04:42:50 +02:00
this . _isOpened = true ;
}
2020-05-11 02:48:41 +02:00
///Closes the [ChromeSafariBrowser] instance.
Future < void > close ( ) async {
Map < String , dynamic > args = < String , dynamic > { } ;
await _channel . invokeMethod ( " close " , args ) ;
}
2022-04-25 17:39:04 +02:00
///Set a custom action button.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsIntent.Builder.setActionButton ](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsIntent.Builder#setActionButton(android.graphics.Bitmap,%20java.lang.String,%20android.app.PendingIntent,%20boolean)))
void setActionButton ( ChromeSafariBrowserActionButton actionButton ) {
this . _actionButton = actionButton ;
}
2020-05-11 02:48:41 +02:00
///Adds a [ChromeSafariBrowserMenuItem] to the menu.
2022-04-17 21:51:15 +02:00
///
///**NOTE**: Not available in an Android Trusted Web Activity.
2020-05-11 02:48:41 +02:00
void addMenuItem ( ChromeSafariBrowserMenuItem menuItem ) {
this . _menuItems [ menuItem . id ] = menuItem ;
}
///Adds a list of [ChromeSafariBrowserMenuItem] to the menu.
2022-04-17 21:51:15 +02:00
///
///**NOTE**: Not available in an Android Trusted Web Activity.
2020-05-11 02:48:41 +02:00
void addMenuItems ( List < ChromeSafariBrowserMenuItem > menuItems ) {
menuItems . forEach ( ( menuItem ) {
this . _menuItems [ menuItem . id ] = menuItem ;
} ) ;
}
2020-06-30 14:29:19 +02:00
///On Android, returns `true` if Chrome Custom Tabs is available.
///On iOS, returns `true` if SFSafariViewController is available.
///Otherwise returns `false`.
static Future < bool > isAvailable ( ) async {
Map < String , dynamic > args = < String , dynamic > { } ;
return await _sharedChannel . invokeMethod ( " isAvailable " , args ) ;
}
2019-10-26 04:42:50 +02:00
///Event fires when the [ChromeSafariBrowser] is opened.
2019-12-01 12:55:06 +01:00
void onOpened ( ) { }
2019-10-26 04:42:50 +02:00
2019-12-18 01:56:21 +01:00
///Event fires when the initial URL load is complete.
void onCompletedInitialLoad ( ) { }
2019-10-26 04:42:50 +02:00
///Event fires when the [ChromeSafariBrowser] is closed.
2019-12-01 12:55:06 +01:00
void onClosed ( ) { }
2019-10-26 04:42:50 +02:00
2019-11-25 23:04:17 +01:00
///Returns `true` if the [ChromeSafariBrowser] instance is opened, otherwise `false`.
2019-10-26 04:42:50 +02:00
bool isOpened ( ) {
return this . _isOpened ;
}
void throwIsAlreadyOpened ( { String message = ' ' } ) {
if ( this . isOpened ( ) ) {
2021-02-22 23:38:30 +01:00
throw ChromeSafariBrowserAlreadyOpenedException ( [
2019-12-01 12:55:06 +01:00
' Error: ${ ( message . isEmpty ) ? ' ' : message + ' ' } The browser is already opened. '
] ) ;
2019-10-26 04:42:50 +02:00
}
}
void throwIsNotOpened ( { String message = ' ' } ) {
if ( ! this . isOpened ( ) ) {
2021-02-22 23:38:30 +01:00
throw ChromeSafariBrowserNotOpenedException ( [
2019-12-01 12:55:06 +01:00
' Error: ${ ( message . isEmpty ) ? ' ' : message + ' ' } The browser is not opened. '
] ) ;
2019-10-26 04:42:50 +02:00
}
}
2019-12-01 12:55:06 +01:00
}
2020-05-11 02:48:41 +02:00
2022-04-25 17:39:04 +02:00
///Class that represents a custom action button for a [ChromeSafariBrowser] instance.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
class ChromeSafariBrowserActionButton {
///The action button id. It should be different from the [ChromeSafariBrowserMenuItem.id].
int id ;
///The icon byte data.
Uint8List icon ;
///The description for the button. To be used for accessibility.
String description ;
///Whether the action button should be tinted.
bool shouldTint ;
///Callback function to be invoked when the menu item is clicked
final void Function ( String url , String title ) action ;
ChromeSafariBrowserActionButton (
2022-04-25 17:43:22 +02:00
{ required this . id ,
required this . icon ,
required this . description ,
required this . action ,
this . shouldTint = false } ) ;
2022-04-25 17:39:04 +02:00
Map < String , dynamic > toMap ( ) {
return {
" id " : id ,
" icon " : icon ,
" description " : description ,
" shouldTint " : shouldTint
} ;
}
Map < String , dynamic > toJson ( ) {
return this . toMap ( ) ;
}
@ override
String toString ( ) {
return toMap ( ) . toString ( ) ;
}
}
2020-05-29 19:56:03 +02:00
///Class that represents a custom menu item for a [ChromeSafariBrowser] instance.
2022-04-17 21:51:15 +02:00
///
///**NOTE**: Not available in an Android Trusted Web Activity.
2020-05-11 02:48:41 +02:00
class ChromeSafariBrowserMenuItem {
2022-04-25 17:39:04 +02:00
///The menu item id. It should be different from [ChromeSafariBrowserActionButton.id].
2020-05-11 02:48:41 +02:00
int id ;
2020-05-29 19:56:03 +02:00
///The label of the menu item
2020-05-11 02:48:41 +02:00
String label ;
2020-05-29 19:56:03 +02:00
///Callback function to be invoked when the menu item is clicked
2020-05-11 02:48:41 +02:00
final void Function ( String url , String title ) action ;
2020-05-29 19:56:03 +02:00
ChromeSafariBrowserMenuItem (
2021-01-28 17:10:15 +01:00
{ required this . id , required this . label , required this . action } ) ;
2020-05-29 19:56:03 +02:00
Map < String , dynamic > toMap ( ) {
return { " id " : id , " label " : label } ;
}
Map < String , dynamic > toJson ( ) {
return this . toMap ( ) ;
}
@ override
String toString ( ) {
return toMap ( ) . toString ( ) ;
}
}