Added support for Android TWA (Trusted Web Activity)

This commit is contained in:
Lorenzo Pichilli 2022-04-17 21:47:35 +02:00
parent c61019058c
commit 5510fd342f
14 changed files with 481 additions and 29 deletions

View File

@ -6,6 +6,7 @@
- Added `singleInstance` option for Android `ChromeSafariBrowser` implementation
- Added `onDownloadStartRequest` event and deprecated old `onDownloadStart` event
- Added `shareState` Android option for `ChromeSafariBrowser` class
- Added support for Android TWA (Trusted Web Activity)
- Fixed missing `onZoomScaleChanged` call for `InAppBrowser` class
- Fixed `requestImageRef` method always `null` on iOS
- Fixed "applicationNameForUserAgent is not work in ios" [#525](https://github.com/pichillilorenzo/flutter_inappwebview/issues/525)

View File

@ -46,8 +46,8 @@ android {
}
dependencies {
implementation 'androidx.webkit:webkit:1.4.0'
implementation 'androidx.browser:browser:1.3.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
}

View File

@ -11,11 +11,20 @@
android:theme="@style/ThemeTransparent"
android:exported="true"
android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeCustomTabsActivity" />
<activity
android:theme="@style/ThemeTransparent"
android:exported="true"
android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.TrustedWebActivity" />
<activity
android:theme="@style/ThemeTransparent"
android:exported="true"
android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeCustomTabsActivitySingleInstance"
android:launchMode="singleInstance"/>
<activity
android:theme="@style/ThemeTransparent"
android:exported="true"
android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.TrustedWebActivitySingleInstance"
android:launchMode="singleInstance"/>
<receiver android:name="com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ActionBroadcastReceiver" />
<meta-data
android:name="io.flutter.embedded_views_preview"

View File

@ -67,17 +67,7 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
customTabActivityHelper.setConnectionCallback(new CustomTabActivityHelper.ConnectionCallback() {
@Override
public void onCustomTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
Uri uri = Uri.parse(url);
customTabActivityHelper.mayLaunchUrl(uri, null, null);
builder = new CustomTabsIntent.Builder(customTabsSession);
prepareCustomTabs(menuItemList);
CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabsIntent(customTabsIntent);
CustomTabActivityHelper.openCustomTab(chromeCustomTabsActivity, customTabsIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
customTabsConnected(url, menuItemList);
}
@Override
@ -149,6 +139,20 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
}
}
public void customTabsConnected (String url, List<HashMap<String, Object>> menuItemList) {
customTabsSession = customTabActivityHelper.getSession();
Uri uri = Uri.parse(url);
customTabActivityHelper.mayLaunchUrl(uri, null, null);
builder = new CustomTabsIntent.Builder(customTabsSession);
prepareCustomTabs(menuItemList);
CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabsIntent(customTabsIntent);
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
private void prepareCustomTabs(List<HashMap<String, Object>> menuItemList) {
if (options.addDefaultShareMenuItem != null) {
builder.setShareState(options.addDefaultShareMenuItem ?

View File

@ -4,10 +4,14 @@ import android.content.Intent;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.trusted.ScreenOrientation;
import androidx.browser.trusted.TrustedWebActivityDisplayMode;
import com.pichillilorenzo.flutter_inappwebview.Options;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChromeCustomTabsOptions implements Options<ChromeCustomTabsActivity> {
@ -24,8 +28,12 @@ public class ChromeCustomTabsOptions implements Options<ChromeCustomTabsActivity
public Boolean instantAppsEnabled = false;
public String packageName;
public Boolean keepAliveEnabled = false;
public Boolean singleInstance = false;
public Boolean isSingleInstance = false;
public Boolean noHistory = false;
public Boolean isTrustedWebActivity = false;
public List<String> additionalTrustedOrigins = new ArrayList<>();
public TrustedWebActivityDisplayMode displayMode = null;
public Integer screenOrientation = ScreenOrientation.DEFAULT;
@Override
public ChromeCustomTabsOptions parse(Map<String, Object> options) {
@ -61,12 +69,35 @@ public class ChromeCustomTabsOptions implements Options<ChromeCustomTabsActivity
case "keepAliveEnabled":
keepAliveEnabled = (Boolean) value;
break;
case "singleInstance":
singleInstance = (Boolean) value;
case "isSingleInstance":
isSingleInstance = (Boolean) value;
break;
case "noHistory":
noHistory = (Boolean) value;
break;
case "isTrustedWebActivity":
isTrustedWebActivity = (Boolean) value;
break;
case "additionalTrustedOrigins":
additionalTrustedOrigins = (List<String>) value;
break;
case "displayMode":
Map<String, Object> displayModeMap = (Map<String, Object>) value;
String displayModeType = (String) displayModeMap.get("type");
if (displayModeType != null) {
switch (displayModeType) {
case "IMMERSIVE_MODE":
boolean isSticky = (boolean) displayModeMap.get("isSticky");
int layoutInDisplayCutoutMode = (int) displayModeMap.get("layoutInDisplayCutoutMode");
displayMode = new TrustedWebActivityDisplayMode.ImmersiveMode(isSticky, layoutInDisplayCutoutMode);
case "DEFAULT_MODE":
displayMode = new TrustedWebActivityDisplayMode.DefaultMode();
}
}
break;
case "screenOrientation":
screenOrientation = (Integer) value;
break;
}
}
@ -83,8 +114,11 @@ public class ChromeCustomTabsOptions implements Options<ChromeCustomTabsActivity
options.put("instantAppsEnabled", instantAppsEnabled);
options.put("packageName", packageName);
options.put("keepAliveEnabled", keepAliveEnabled);
options.put("singleInstance", singleInstance);
options.put("isSingleInstance", isSingleInstance);
options.put("noHistory", noHistory);
options.put("isTrustedWebActivity", isTrustedWebActivity);
options.put("additionalTrustedOrigins", additionalTrustedOrigins);
options.put("screenOrientation", screenOrientation);
return options;
}

View File

@ -68,11 +68,15 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
extras.putSerializable("options", options);
extras.putSerializable("menuItemList", (Serializable) menuItemList);
Boolean isSingleInstance = (Boolean) options.get("singleInstance");
Boolean isSingleInstance = (Boolean) Util.getOrDefault(options, "isSingleInstance", false);
Boolean isTrustedWebActivity = (Boolean) Util.getOrDefault(options, "isTrustedWebActivity", false);
if (CustomTabActivityHelper.isAvailable(activity)) {
intent = new Intent(activity, !isSingleInstance ? ChromeCustomTabsActivity.class : ChromeCustomTabsActivitySingleInstance.class);
intent = new Intent(activity, !isSingleInstance ?
(!isTrustedWebActivity ? ChromeCustomTabsActivity.class : TrustedWebActivity.class) :
(!isTrustedWebActivity ? ChromeCustomTabsActivitySingleInstance.class : TrustedWebActivitySingleInstance.class));
intent.putExtras(extras);
if ((Boolean) options.get("noHistory")) {
Boolean noHistory = (Boolean) Util.getOrDefault(options, "noHistory", false);
if (noHistory) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(intent);

View File

@ -1,6 +1,7 @@
package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
@ -9,6 +10,7 @@ import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.trusted.TrustedWebActivityIntent;
import java.util.List;
@ -37,6 +39,14 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
activity.startActivityForResult(customTabsIntent.intent, requestCode);
}
public static void openCustomTab(Activity activity,
TrustedWebActivityIntent trustedWebActivityIntent,
Uri uri,
int requestCode) {
trustedWebActivityIntent.getIntent().setData(uri);
activity.startActivityForResult(trustedWebActivityIntent.getIntent(), requestCode);
}
public static boolean isAvailable(Activity activity) {
return CustomTabsHelper.getPackageNameToUse(activity) != null;
}
@ -135,5 +145,4 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
*/
void onCustomTabsDisconnected();
}
}

View File

@ -0,0 +1,64 @@
package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.trusted.TrustedWebActivityIntent;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import java.util.HashMap;
import java.util.List;
public class TrustedWebActivity extends ChromeCustomTabsActivity {
protected static final String LOG_TAG = "TrustedWebActivity";
public TrustedWebActivityIntentBuilder builder;
@Override
public void customTabsConnected (String url, List<HashMap<String, Object>> menuItemList) {
customTabsSession = customTabActivityHelper.getSession();
Uri uri = Uri.parse(url);
customTabActivityHelper.mayLaunchUrl(uri, null, null);
builder = new TrustedWebActivityIntentBuilder(uri);
prepareCustomTabs();
TrustedWebActivityIntent trustedWebActivityIntent = builder.build(customTabsSession);
prepareCustomTabsIntent(trustedWebActivityIntent);
CustomTabActivityHelper.openCustomTab(this, trustedWebActivityIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
private void prepareCustomTabs() {
if (options.toolbarBackgroundColor != null && !options.toolbarBackgroundColor.isEmpty()) {
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder
.setToolbarColor(Color.parseColor(options.toolbarBackgroundColor))
.build());
}
if (options.additionalTrustedOrigins != null && !options.additionalTrustedOrigins.isEmpty()) {
builder.setAdditionalTrustedOrigins(options.additionalTrustedOrigins);
}
if (options.displayMode != null) {
builder.setDisplayMode(options.displayMode);
}
builder.setScreenOrientation(options.screenOrientation);
}
private void prepareCustomTabsIntent(TrustedWebActivityIntent trustedWebActivityIntent) {
Intent intent = trustedWebActivityIntent.getIntent();
if (options.packageName != null)
intent.setPackage(options.packageName);
else
intent.setPackage(CustomTabsHelper.getPackageNameToUse(this));
if (options.keepAliveEnabled)
CustomTabsHelper.addKeepAliveExtra(this, intent);
}
}

View File

@ -0,0 +1,7 @@
package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
public class TrustedWebActivitySingleInstance extends TrustedWebActivity {
protected static final String LOG_TAG = "TrustedWebActivitySingleInstance";
}

View File

@ -30,6 +30,10 @@
android:label="flutter_inappwebview_example"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="asset_statements"
android:resource="@string/asset_statements" />
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

View File

@ -0,0 +1,11 @@
<resources>
<string name="app_name">Flutter Trusted Web Activity</string>
<string name="asset_statements">
[{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://flutter.dev\"}
}]
</string>
</resources>

View File

@ -67,7 +67,8 @@ class _ChromeSafariBrowserExampleScreenState
options: ChromeSafariBrowserClassOptions(
android: AndroidChromeCustomTabsOptions(
shareState: CustomTabsShareState.SHARE_STATE_OFF,
singleInstance: false,
isSingleInstance: false,
isTrustedWebActivity: false,
keepAliveEnabled: true),
ios: IOSSafariOptions(
dismissButtonStyle:

View File

@ -11,23 +11,31 @@ import '../../in_app_webview/android/in_app_webview_options.dart';
///This class represents all the Android-only [ChromeSafariBrowser] options available.
class AndroidChromeCustomTabsOptions
implements ChromeSafariBrowserOptions, AndroidOptions {
///Set to `false` if you don't want the default share item to the menu. The default value is `true`.
///Use `shareState` instead.
@Deprecated('Use `shareState` instead')
bool? addDefaultShareMenuItem;
///The share state that should be applied to the custom tab. The default value is [CustomTabsShareState.SHARE_STATE_DEFAULT].
///
///**NOTE**: Not available in a Trusted Web Activity.
CustomTabsShareState shareState;
///Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
///
///**NOTE**: Not available in a Trusted Web Activity.
bool showTitle;
///Set the custom background color of the toolbar.
Color? toolbarBackgroundColor;
///Set to `true` to enable the url bar to hide as the user scrolls down on the page. The default value is `false`.
///
///**NOTE**: Not available in a Trusted Web Activity.
bool enableUrlBarHiding;
///Set to `true` to enable Instant Apps. The default value is `false`.
///
///**NOTE**: Not available in a Trusted Web Activity.
bool instantAppsEnabled;
///Set an explicit application package name that limits
@ -41,11 +49,29 @@ class AndroidChromeCustomTabsOptions
bool keepAliveEnabled;
///Set to `true` to launch the Android activity in `singleInstance` mode. The default value is `false`.
bool singleInstance;
bool isSingleInstance;
///Set to `true` to launch the Android intent with the flag `FLAG_ACTIVITY_NO_HISTORY`. The default value is `false`.
bool noHistory;
///Set to `true` to launch the Custom Tab as a Trusted Web Activity. The default value is `false`.
bool isTrustedWebActivity;
///Sets a list of additional trusted origins that the user may navigate or be redirected to from the starting uri.
///
///**NOTE**: Available only in a Trusted Web Activity.
List<String> additionalTrustedOrigins;
///Sets a display mode of a Trusted Web Activity.
///
///**NOTE**: Available only in a Trusted Web Activity.
TrustedWebActivityDisplayMode? displayMode;
///Sets a screen orientation. This can be used e.g. to enable the locking of an orientation lock type.
///
///**NOTE**: Available only in a Trusted Web Activity.
TrustedWebActivityScreenOrientation screenOrientation;
AndroidChromeCustomTabsOptions(
{@Deprecated('Use `shareState` instead') this.addDefaultShareMenuItem,
this.shareState = CustomTabsShareState.SHARE_STATE_DEFAULT,
@ -55,8 +81,12 @@ class AndroidChromeCustomTabsOptions
this.instantAppsEnabled = false,
this.packageName,
this.keepAliveEnabled = false,
this.singleInstance = false,
this.noHistory = false});
this.isSingleInstance = false,
this.noHistory = false,
this.isTrustedWebActivity = false,
this.additionalTrustedOrigins = const [],
this.displayMode,
this.screenOrientation = TrustedWebActivityScreenOrientation.DEFAULT});
@override
Map<String, dynamic> toMap() {
@ -70,8 +100,12 @@ class AndroidChromeCustomTabsOptions
"instantAppsEnabled": instantAppsEnabled,
"packageName": packageName,
"keepAliveEnabled": keepAliveEnabled,
"singleInstance": singleInstance,
"noHistory": noHistory
"isSingleInstance": isSingleInstance,
"noHistory": noHistory,
"isTrustedWebActivity": isTrustedWebActivity,
"additionalTrustedOrigins": additionalTrustedOrigins,
"displayMode": displayMode?.toMap(),
"screenOrientation": screenOrientation.toValue()
};
}
@ -88,8 +122,20 @@ class AndroidChromeCustomTabsOptions
options.instantAppsEnabled = map["instantAppsEnabled"];
options.packageName = map["packageName"];
options.keepAliveEnabled = map["keepAliveEnabled"];
options.singleInstance = map["singleInstance"];
options.isSingleInstance = map["isSingleInstance"];
options.noHistory = map["noHistory"];
options.isTrustedWebActivity = map["isTrustedWebActivity"];
options.additionalTrustedOrigins = map["additionalTrustedOrigins"];
switch(map["displayMode"]["type"]) {
case "IMMERSIVE_MODE":
options.displayMode = TrustedWebActivityImmersiveDisplayMode.fromMap(map["displayMode"]);
break;
case "DEFAULT_MODE":
default:
options.displayMode = TrustedWebActivityDefaultDisplayMode();
break;
}
options.screenOrientation = map["screenOrientation"];
return options;
}

View File

@ -7012,6 +7012,264 @@ class CustomTabsShareState {
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
///Android-class that represents display mode of a Trusted Web Activity.
abstract class TrustedWebActivityDisplayMode {
Map<String, dynamic> toMap() {
return {};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Android-class that represents the default display mode of a Trusted Web Activity.
///The system UI (status bar, navigation bar) is shown, and the browser toolbar is hidden while the user is on a verified origin.
class TrustedWebActivityDefaultDisplayMode implements TrustedWebActivityDisplayMode {
String _type = "DEFAULT_MODE";
Map<String, dynamic> toMap() {
return {
"type": _type
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Android-class that represents the default display mode of a Trusted Web Activity.
///The system UI (status bar, navigation bar) is shown, and the browser toolbar is hidden while the user is on a verified origin.
class TrustedWebActivityImmersiveDisplayMode implements TrustedWebActivityDisplayMode {
///Whether the Trusted Web Activity should be in sticky immersive mode.
bool isSticky;
///The constant defining how to deal with display cutouts.
AndroidLayoutInDisplayCutoutMode layoutInDisplayCutoutMode;
String _type = "IMMERSIVE_MODE";
TrustedWebActivityImmersiveDisplayMode(
{required this.isSticky,
required this.layoutInDisplayCutoutMode});
static TrustedWebActivityImmersiveDisplayMode? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
return TrustedWebActivityImmersiveDisplayMode(
isSticky: map["isSticky"],
layoutInDisplayCutoutMode: map["layoutInDisplayCutoutMode"]);
}
Map<String, dynamic> toMap() {
return {
"isSticky": isSticky,
"layoutInDisplayCutoutMode": layoutInDisplayCutoutMode.toValue(),
"type": _type
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Android-specific class representing the share state that should be applied to the custom tab.
///
///**NOTE**: available on Android 28+.
class AndroidLayoutInDisplayCutoutMode {
final int _value;
const AndroidLayoutInDisplayCutoutMode._internal(this._value);
static final Set<AndroidLayoutInDisplayCutoutMode> values = [
AndroidLayoutInDisplayCutoutMode.DEFAULT,
AndroidLayoutInDisplayCutoutMode.SHORT_EDGES,
AndroidLayoutInDisplayCutoutMode.NEVER,
AndroidLayoutInDisplayCutoutMode.ALWAYS
].toSet();
static AndroidLayoutInDisplayCutoutMode? fromValue(int? value) {
if (value != null) {
try {
return AndroidLayoutInDisplayCutoutMode.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
int toValue() => _value;
@override
String toString() {
switch (_value) {
case 1:
return "SHORT_EDGES";
case 2:
return "NEVER";
case 3:
return "ALWAYS";
case 0:
default:
return "DEFAULT";
}
}
///With this default setting, content renders into the cutout area when displayed in portrait mode, but content is letterboxed when displayed in landscape mode.
///
///**NOTE**: available on Android 28+.
static const DEFAULT = const AndroidLayoutInDisplayCutoutMode._internal(0);
///Content renders into the cutout area in both portrait and landscape modes.
///
///**NOTE**: available on Android 28+.
static const SHORT_EDGES = const AndroidLayoutInDisplayCutoutMode._internal(1);
///Content never renders into the cutout area.
///
///**NOTE**: available on Android 28+.
static const NEVER = const AndroidLayoutInDisplayCutoutMode._internal(2);
///The window is always allowed to extend into the DisplayCutout areas on the all edges of the screen.
///
///**NOTE**: available on Android 30+.
static const ALWAYS = const AndroidLayoutInDisplayCutoutMode._internal(3);
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
/// Android-specific class representing Screen Orientation Lock type value of a Trusted Web Activity:
/// https://www.w3.org/TR/screen-orientation/#screenorientation-interface
class TrustedWebActivityScreenOrientation {
final int _value;
const TrustedWebActivityScreenOrientation._internal(this._value);
static final Set<TrustedWebActivityScreenOrientation> values = [
TrustedWebActivityScreenOrientation.DEFAULT,
TrustedWebActivityScreenOrientation.PORTRAIT_PRIMARY,
TrustedWebActivityScreenOrientation.PORTRAIT_SECONDARY,
TrustedWebActivityScreenOrientation.LANDSCAPE_PRIMARY,
TrustedWebActivityScreenOrientation.LANDSCAPE_SECONDARY,
TrustedWebActivityScreenOrientation.ANY,
TrustedWebActivityScreenOrientation.LANDSCAPE,
TrustedWebActivityScreenOrientation.PORTRAIT,
TrustedWebActivityScreenOrientation.NATURAL,
].toSet();
static TrustedWebActivityScreenOrientation? fromValue(int? value) {
if (value != null) {
try {
return TrustedWebActivityScreenOrientation.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
int toValue() => _value;
@override
String toString() {
switch (_value) {
case 1:
return "PORTRAIT_PRIMARY";
case 2:
return "PORTRAIT_SECONDARY";
case 3:
return "LANDSCAPE_PRIMARY";
case 4:
return "LANDSCAPE_SECONDARY";
case 5:
return "ANY";
case 6:
return "LANDSCAPE";
case 7:
return "PORTRAIT";
case 8:
return "NATURAL";
case 0:
default:
return "DEFAULT";
}
}
/// The default screen orientation is the set of orientations to which the screen is locked when
/// there is no current orientation lock.
static const DEFAULT = const TrustedWebActivityScreenOrientation._internal(0);
/// Portrait-primary is an orientation where the screen width is less than or equal to the
/// screen height. If the device's natural orientation is portrait, then it is in
/// portrait-primary when held in that position.
static const PORTRAIT_PRIMARY = const TrustedWebActivityScreenOrientation._internal(1);
/// Portrait-secondary is an orientation where the screen width is less than or equal to the
/// screen height. If the device's natural orientation is portrait, then it is in
/// portrait-secondary when rotated 180° from its natural position.
static const PORTRAIT_SECONDARY = const TrustedWebActivityScreenOrientation._internal(2);
/// Landscape-primary is an orientation where the screen width is greater than the screen height.
/// If the device's natural orientation is landscape, then it is in landscape-primary when held
/// in that position.
static const LANDSCAPE_PRIMARY = const TrustedWebActivityScreenOrientation._internal(3);
/// Landscape-secondary is an orientation where the screen width is greater than the
/// screen height. If the device's natural orientation is landscape, it is in
/// landscape-secondary when rotated 180° from its natural orientation.
static const LANDSCAPE_SECONDARY = const TrustedWebActivityScreenOrientation._internal(4);
/// Any is an orientation that means the screen can be locked to any one of portrait-primary,
/// portrait-secondary, landscape-primary and landscape-secondary.
static const ANY = const TrustedWebActivityScreenOrientation._internal(5);
/// Landscape is an orientation where the screen width is greater than the screen height and
/// depending on platform convention locking the screen to landscape can represent
/// landscape-primary, landscape-secondary or both.
static const LANDSCAPE = const TrustedWebActivityScreenOrientation._internal(6);
/// Portrait is an orientation where the screen width is less than or equal to the screen height
/// and depending on platform convention locking the screen to portrait can represent
/// portrait-primary, portrait-secondary or both.
static const PORTRAIT = const TrustedWebActivityScreenOrientation._internal(7);
/// Natural is an orientation that refers to either portrait-primary or landscape-primary
/// depending on the device's usual orientation. This orientation is usually provided by
/// the underlying operating system.
static const NATURAL = const TrustedWebActivityScreenOrientation._internal(8);
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}