diff --git a/app/lib/core/routing/routes.g.dart b/app/lib/core/routing/routes.g.dart index 5fdf0547..c4473637 100644 --- a/app/lib/core/routing/routes.g.dart +++ b/app/lib/core/routing/routes.g.dart @@ -9,10 +9,10 @@ part of 'routes.dart'; List get $appRoutes => [ $aboutRoute, $onboardingRoute, - $settingsRoute, - $browserRoute, $bangMenuRoute, + $browserRoute, $feedListRoute, + $settingsRoute, ]; RouteBase get $aboutRoute => GoRouteData.$route( @@ -74,101 +74,60 @@ mixin $OnboardingRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $settingsRoute => GoRouteData.$route( - path: '/settings', - name: 'SettingsRoute', - factory: $SettingsRoute._fromState, +RouteBase get $bangMenuRoute => GoRouteData.$route( + path: '/bangs', + name: 'BangRoute', + factory: $BangMenuRoute._fromState, routes: [ GoRouteData.$route( - path: 'appearance_display', - name: 'AppearanceDisplaySettingsRoute', - factory: $AppearanceDisplaySettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'privacy_security', - name: 'PrivacySecuritySettingsRoute', - factory: $PrivacySecuritySettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'search_content', - name: 'SearchContentSettingsRoute', - factory: $SearchContentSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'tabs_behavior', - name: 'TabsBehaviorSettingsRoute', - factory: $TabsBehaviorSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'fingerprinting', - name: 'FingerprintingSettingsRoute', - factory: $FingerprintingSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'advanced', - name: 'AdvancedSettingsRoute', - factory: $AdvancedSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'bang', - name: 'BangSettingsRoute', - factory: $BangSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'hardening', - name: 'WebEngineHardeningRoute', - factory: $WebEngineHardeningRoute._fromState, + path: 'user', + name: 'UserBangsRoute', + factory: $UserBangsRoute._fromState, routes: [ GoRouteData.$route( - path: 'group/:group', - name: 'WebEngineHardeningGroupRoute', - factory: $WebEngineHardeningGroupRoute._fromState, + path: 'new', + name: 'NewUserBangRoute', + factory: $NewUserBangRoute._fromState, + ), + GoRouteData.$route( + path: 'edit', + name: 'EditUserBangRoute', + factory: $EditUserBangRoute._fromState, ), ], ), GoRouteData.$route( - path: 'doh', - name: 'DohSettingsRoute', - factory: $DohSettingsRoute._fromState, + path: 'search/:searchText', + name: 'BangSearchRoute', + factory: $BangSearchRoute._fromState, ), GoRouteData.$route( - path: 'fingerprint', - name: 'FingerprintSettingsRoute', - factory: $FingerprintSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'locales', - name: 'LocaleSettingsRoute', - factory: $LocaleSettingsRoute._fromState, - ), - GoRouteData.$route( - path: 'addon_collection', - name: 'AddonCollectionRoute', - factory: $AddonCollectionRoute._fromState, - ), - GoRouteData.$route( - path: 'tracking_protection_exceptions', - name: 'TrackingProtectionExceptionsRoute', - factory: $TrackingProtectionExceptionsRoute._fromState, - ), - GoRouteData.$route( - path: 'custom_tracking_protection', - name: 'CustomTrackingProtectionRoute', - factory: $CustomTrackingProtectionRoute._fromState, - ), - GoRouteData.$route( - path: 'error_logs', - name: 'ErrorLogsRoute', - factory: $ErrorLogsRoute._fromState, + path: 'categories', + name: 'BangCategoriesRoute', + factory: $BangCategoriesRoute._fromState, + routes: [ + GoRouteData.$route( + path: 'category/:category', + name: 'BangCategoryRoute', + factory: $BangCategoryRoute._fromState, + routes: [ + GoRouteData.$route( + path: ':subCategory', + name: 'BangSubCategoryRoute', + factory: $BangSubCategoryRoute._fromState, + ), + ], + ), + ], ), ], ); -mixin $SettingsRoute on GoRouteData { - static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(); +mixin $BangMenuRoute on GoRouteData { + static BangMenuRoute _fromState(GoRouterState state) => const BangMenuRoute(); @override - String get location => GoRouteData.$location('/settings'); + String get location => GoRouteData.$location('/bangs'); @override void go(BuildContext context) => context.go(location); @@ -184,12 +143,12 @@ mixin $SettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $AppearanceDisplaySettingsRoute on GoRouteData { - static AppearanceDisplaySettingsRoute _fromState(GoRouterState state) => - AppearanceDisplaySettingsRoute(); +mixin $UserBangsRoute on GoRouteData { + static UserBangsRoute _fromState(GoRouterState state) => + const UserBangsRoute(); @override - String get location => GoRouteData.$location('/settings/appearance_display'); + String get location => GoRouteData.$location('/bangs/user'); @override void go(BuildContext context) => context.go(location); @@ -205,12 +164,12 @@ mixin $AppearanceDisplaySettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $PrivacySecuritySettingsRoute on GoRouteData { - static PrivacySecuritySettingsRoute _fromState(GoRouterState state) => - PrivacySecuritySettingsRoute(); +mixin $NewUserBangRoute on GoRouteData { + static NewUserBangRoute _fromState(GoRouterState state) => + const NewUserBangRoute(); @override - String get location => GoRouteData.$location('/settings/privacy_security'); + String get location => GoRouteData.$location('/bangs/user/new'); @override void go(BuildContext context) => context.go(location); @@ -226,142 +185,17 @@ mixin $PrivacySecuritySettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $SearchContentSettingsRoute on GoRouteData { - static SearchContentSettingsRoute _fromState(GoRouterState state) => - SearchContentSettingsRoute(); +mixin $EditUserBangRoute on GoRouteData { + static EditUserBangRoute _fromState(GoRouterState state) => EditUserBangRoute( + initialBang: state.uri.queryParameters['initial-bang']!, + ); - @override - String get location => GoRouteData.$location('/settings/search_content'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $TabsBehaviorSettingsRoute on GoRouteData { - static TabsBehaviorSettingsRoute _fromState(GoRouterState state) => - TabsBehaviorSettingsRoute(); - - @override - String get location => GoRouteData.$location('/settings/tabs_behavior'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $FingerprintingSettingsRoute on GoRouteData { - static FingerprintingSettingsRoute _fromState(GoRouterState state) => - FingerprintingSettingsRoute(); - - @override - String get location => GoRouteData.$location('/settings/fingerprinting'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $AdvancedSettingsRoute on GoRouteData { - static AdvancedSettingsRoute _fromState(GoRouterState state) => - AdvancedSettingsRoute(); - - @override - String get location => GoRouteData.$location('/settings/advanced'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $BangSettingsRoute on GoRouteData { - static BangSettingsRoute _fromState(GoRouterState state) => - BangSettingsRoute(); - - @override - String get location => GoRouteData.$location('/settings/bang'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $WebEngineHardeningRoute on GoRouteData { - static WebEngineHardeningRoute _fromState(GoRouterState state) => - WebEngineHardeningRoute(); - - @override - String get location => GoRouteData.$location('/settings/hardening'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $WebEngineHardeningGroupRoute on GoRouteData { - static WebEngineHardeningGroupRoute _fromState(GoRouterState state) => - WebEngineHardeningGroupRoute(group: state.pathParameters['group']!); - - WebEngineHardeningGroupRoute get _self => - this as WebEngineHardeningGroupRoute; + EditUserBangRoute get _self => this as EditUserBangRoute; @override String get location => GoRouteData.$location( - '/settings/hardening/group/${Uri.encodeComponent(_self.group)}', + '/bangs/user/edit', + queryParams: {'initial-bang': _self.initialBang}, ); @override @@ -378,11 +212,18 @@ mixin $WebEngineHardeningGroupRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $DohSettingsRoute on GoRouteData { - static DohSettingsRoute _fromState(GoRouterState state) => DohSettingsRoute(); +mixin $BangSearchRoute on GoRouteData { + static BangSearchRoute _fromState(GoRouterState state) => BangSearchRoute( + searchText: + state.pathParameters['searchText'] ?? BangSearchRoute.emptySearchText, + ); + + BangSearchRoute get _self => this as BangSearchRoute; @override - String get location => GoRouteData.$location('/settings/doh'); + String get location => GoRouteData.$location( + '/bangs/search/${Uri.encodeComponent(_self.searchText)}', + ); @override void go(BuildContext context) => context.go(location); @@ -398,12 +239,12 @@ mixin $DohSettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $FingerprintSettingsRoute on GoRouteData { - static FingerprintSettingsRoute _fromState(GoRouterState state) => - FingerprintSettingsRoute(); +mixin $BangCategoriesRoute on GoRouteData { + static BangCategoriesRoute _fromState(GoRouterState state) => + const BangCategoriesRoute(); @override - String get location => GoRouteData.$location('/settings/fingerprint'); + String get location => GoRouteData.$location('/bangs/categories'); @override void go(BuildContext context) => context.go(location); @@ -419,12 +260,16 @@ mixin $FingerprintSettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $LocaleSettingsRoute on GoRouteData { - static LocaleSettingsRoute _fromState(GoRouterState state) => - LocaleSettingsRoute(); +mixin $BangCategoryRoute on GoRouteData { + static BangCategoryRoute _fromState(GoRouterState state) => + BangCategoryRoute(category: state.pathParameters['category']!); + + BangCategoryRoute get _self => this as BangCategoryRoute; @override - String get location => GoRouteData.$location('/settings/locales'); + String get location => GoRouteData.$location( + '/bangs/categories/category/${Uri.encodeComponent(_self.category)}', + ); @override void go(BuildContext context) => context.go(location); @@ -440,76 +285,19 @@ mixin $LocaleSettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -mixin $AddonCollectionRoute on GoRouteData { - static AddonCollectionRoute _fromState(GoRouterState state) => - AddonCollectionRoute(); +mixin $BangSubCategoryRoute on GoRouteData { + static BangSubCategoryRoute _fromState(GoRouterState state) => + BangSubCategoryRoute( + category: state.pathParameters['category']!, + subCategory: state.pathParameters['subCategory']!, + ); + + BangSubCategoryRoute get _self => this as BangSubCategoryRoute; @override - String get location => GoRouteData.$location('/settings/addon_collection'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $TrackingProtectionExceptionsRoute on GoRouteData { - static TrackingProtectionExceptionsRoute _fromState(GoRouterState state) => - TrackingProtectionExceptionsRoute(); - - @override - String get location => - GoRouteData.$location('/settings/tracking_protection_exceptions'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $CustomTrackingProtectionRoute on GoRouteData { - static CustomTrackingProtectionRoute _fromState(GoRouterState state) => - CustomTrackingProtectionRoute(); - - @override - String get location => - GoRouteData.$location('/settings/custom_tracking_protection'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $ErrorLogsRoute on GoRouteData { - static ErrorLogsRoute _fromState(GoRouterState state) => ErrorLogsRoute(); - - @override - String get location => GoRouteData.$location('/settings/error_logs'); + String get location => GoRouteData.$location( + '/bangs/categories/category/${Uri.encodeComponent(_self.category)}/${Uri.encodeComponent(_self.subCategory)}', + ); @override void go(BuildContext context) => context.go(location); @@ -1305,245 +1093,6 @@ extension on Map { entries.where((element) => element.value == value).firstOrNull?.key; } -RouteBase get $bangMenuRoute => GoRouteData.$route( - path: '/bangs', - name: 'BangRoute', - factory: $BangMenuRoute._fromState, - routes: [ - GoRouteData.$route( - path: 'user', - name: 'UserBangsRoute', - factory: $UserBangsRoute._fromState, - routes: [ - GoRouteData.$route( - path: 'new', - name: 'NewUserBangRoute', - factory: $NewUserBangRoute._fromState, - ), - GoRouteData.$route( - path: 'edit', - name: 'EditUserBangRoute', - factory: $EditUserBangRoute._fromState, - ), - ], - ), - GoRouteData.$route( - path: 'search/:searchText', - name: 'BangSearchRoute', - factory: $BangSearchRoute._fromState, - ), - GoRouteData.$route( - path: 'categories', - name: 'BangCategoriesRoute', - factory: $BangCategoriesRoute._fromState, - routes: [ - GoRouteData.$route( - path: 'category/:category', - name: 'BangCategoryRoute', - factory: $BangCategoryRoute._fromState, - routes: [ - GoRouteData.$route( - path: ':subCategory', - name: 'BangSubCategoryRoute', - factory: $BangSubCategoryRoute._fromState, - ), - ], - ), - ], - ), - ], -); - -mixin $BangMenuRoute on GoRouteData { - static BangMenuRoute _fromState(GoRouterState state) => const BangMenuRoute(); - - @override - String get location => GoRouteData.$location('/bangs'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $UserBangsRoute on GoRouteData { - static UserBangsRoute _fromState(GoRouterState state) => - const UserBangsRoute(); - - @override - String get location => GoRouteData.$location('/bangs/user'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $NewUserBangRoute on GoRouteData { - static NewUserBangRoute _fromState(GoRouterState state) => - const NewUserBangRoute(); - - @override - String get location => GoRouteData.$location('/bangs/user/new'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $EditUserBangRoute on GoRouteData { - static EditUserBangRoute _fromState(GoRouterState state) => EditUserBangRoute( - initialBang: state.uri.queryParameters['initial-bang']!, - ); - - EditUserBangRoute get _self => this as EditUserBangRoute; - - @override - String get location => GoRouteData.$location( - '/bangs/user/edit', - queryParams: {'initial-bang': _self.initialBang}, - ); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $BangSearchRoute on GoRouteData { - static BangSearchRoute _fromState(GoRouterState state) => BangSearchRoute( - searchText: - state.pathParameters['searchText'] ?? BangSearchRoute.emptySearchText, - ); - - BangSearchRoute get _self => this as BangSearchRoute; - - @override - String get location => GoRouteData.$location( - '/bangs/search/${Uri.encodeComponent(_self.searchText)}', - ); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $BangCategoriesRoute on GoRouteData { - static BangCategoriesRoute _fromState(GoRouterState state) => - const BangCategoriesRoute(); - - @override - String get location => GoRouteData.$location('/bangs/categories'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $BangCategoryRoute on GoRouteData { - static BangCategoryRoute _fromState(GoRouterState state) => - BangCategoryRoute(category: state.pathParameters['category']!); - - BangCategoryRoute get _self => this as BangCategoryRoute; - - @override - String get location => GoRouteData.$location( - '/bangs/categories/category/${Uri.encodeComponent(_self.category)}', - ); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -mixin $BangSubCategoryRoute on GoRouteData { - static BangSubCategoryRoute _fromState(GoRouterState state) => - BangSubCategoryRoute( - category: state.pathParameters['category']!, - subCategory: state.pathParameters['subCategory']!, - ); - - BangSubCategoryRoute get _self => this as BangSubCategoryRoute; - - @override - String get location => GoRouteData.$location( - '/bangs/categories/category/${Uri.encodeComponent(_self.category)}/${Uri.encodeComponent(_self.subCategory)}', - ); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - RouteBase get $feedListRoute => GoRouteData.$route( path: '/feeds', name: 'FeedListRoute', @@ -1752,3 +1301,454 @@ mixin $FeedEditRoute on GoRouteData { @override void replace(BuildContext context) => context.replace(location); } + +RouteBase get $settingsRoute => GoRouteData.$route( + path: '/settings', + name: 'SettingsRoute', + factory: $SettingsRoute._fromState, + routes: [ + GoRouteData.$route( + path: 'appearance_display', + name: 'AppearanceDisplaySettingsRoute', + factory: $AppearanceDisplaySettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'privacy_security', + name: 'PrivacySecuritySettingsRoute', + factory: $PrivacySecuritySettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'search_content', + name: 'SearchContentSettingsRoute', + factory: $SearchContentSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'tabs_behavior', + name: 'TabsBehaviorSettingsRoute', + factory: $TabsBehaviorSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'fingerprinting', + name: 'FingerprintingSettingsRoute', + factory: $FingerprintingSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'advanced', + name: 'AdvancedSettingsRoute', + factory: $AdvancedSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'bang', + name: 'BangSettingsRoute', + factory: $BangSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'hardening', + name: 'WebEngineHardeningRoute', + factory: $WebEngineHardeningRoute._fromState, + routes: [ + GoRouteData.$route( + path: 'group/:group', + name: 'WebEngineHardeningGroupRoute', + factory: $WebEngineHardeningGroupRoute._fromState, + ), + ], + ), + GoRouteData.$route( + path: 'doh', + name: 'DohSettingsRoute', + factory: $DohSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'fingerprint', + name: 'FingerprintSettingsRoute', + factory: $FingerprintSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'locales', + name: 'LocaleSettingsRoute', + factory: $LocaleSettingsRoute._fromState, + ), + GoRouteData.$route( + path: 'addon_collection', + name: 'AddonCollectionRoute', + factory: $AddonCollectionRoute._fromState, + ), + GoRouteData.$route( + path: 'tracking_protection_exceptions', + name: 'TrackingProtectionExceptionsRoute', + factory: $TrackingProtectionExceptionsRoute._fromState, + ), + GoRouteData.$route( + path: 'custom_tracking_protection', + name: 'CustomTrackingProtectionRoute', + factory: $CustomTrackingProtectionRoute._fromState, + ), + GoRouteData.$route( + path: 'error_logs', + name: 'ErrorLogsRoute', + factory: $ErrorLogsRoute._fromState, + ), + ], +); + +mixin $SettingsRoute on GoRouteData { + static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $AppearanceDisplaySettingsRoute on GoRouteData { + static AppearanceDisplaySettingsRoute _fromState(GoRouterState state) => + AppearanceDisplaySettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/appearance_display'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $PrivacySecuritySettingsRoute on GoRouteData { + static PrivacySecuritySettingsRoute _fromState(GoRouterState state) => + PrivacySecuritySettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/privacy_security'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $SearchContentSettingsRoute on GoRouteData { + static SearchContentSettingsRoute _fromState(GoRouterState state) => + SearchContentSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/search_content'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $TabsBehaviorSettingsRoute on GoRouteData { + static TabsBehaviorSettingsRoute _fromState(GoRouterState state) => + TabsBehaviorSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/tabs_behavior'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $FingerprintingSettingsRoute on GoRouteData { + static FingerprintingSettingsRoute _fromState(GoRouterState state) => + FingerprintingSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/fingerprinting'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $AdvancedSettingsRoute on GoRouteData { + static AdvancedSettingsRoute _fromState(GoRouterState state) => + AdvancedSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/advanced'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $BangSettingsRoute on GoRouteData { + static BangSettingsRoute _fromState(GoRouterState state) => + BangSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/bang'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $WebEngineHardeningRoute on GoRouteData { + static WebEngineHardeningRoute _fromState(GoRouterState state) => + WebEngineHardeningRoute(); + + @override + String get location => GoRouteData.$location('/settings/hardening'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $WebEngineHardeningGroupRoute on GoRouteData { + static WebEngineHardeningGroupRoute _fromState(GoRouterState state) => + WebEngineHardeningGroupRoute(group: state.pathParameters['group']!); + + WebEngineHardeningGroupRoute get _self => + this as WebEngineHardeningGroupRoute; + + @override + String get location => GoRouteData.$location( + '/settings/hardening/group/${Uri.encodeComponent(_self.group)}', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $DohSettingsRoute on GoRouteData { + static DohSettingsRoute _fromState(GoRouterState state) => DohSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/doh'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $FingerprintSettingsRoute on GoRouteData { + static FingerprintSettingsRoute _fromState(GoRouterState state) => + FingerprintSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/fingerprint'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $LocaleSettingsRoute on GoRouteData { + static LocaleSettingsRoute _fromState(GoRouterState state) => + LocaleSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/locales'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $AddonCollectionRoute on GoRouteData { + static AddonCollectionRoute _fromState(GoRouterState state) => + AddonCollectionRoute(); + + @override + String get location => GoRouteData.$location('/settings/addon_collection'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $TrackingProtectionExceptionsRoute on GoRouteData { + static TrackingProtectionExceptionsRoute _fromState(GoRouterState state) => + TrackingProtectionExceptionsRoute(); + + @override + String get location => + GoRouteData.$location('/settings/tracking_protection_exceptions'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $CustomTrackingProtectionRoute on GoRouteData { + static CustomTrackingProtectionRoute _fromState(GoRouterState state) => + CustomTrackingProtectionRoute(); + + @override + String get location => + GoRouteData.$location('/settings/custom_tracking_protection'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $ErrorLogsRoute on GoRouteData { + static ErrorLogsRoute _fromState(GoRouterState state) => ErrorLogsRoute(); + + @override + String get location => GoRouteData.$location('/settings/error_logs'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/app/lib/features/geckoview/features/browser/domain/providers.dart b/app/lib/features/geckoview/features/browser/domain/providers.dart index 53f77dc8..7baf5241 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.dart @@ -19,6 +19,7 @@ */ import 'package:collection/collection.dart'; import 'package:fast_equatable/fast_equatable.dart'; +import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:nullability/nullability.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -375,3 +376,18 @@ EquatableValue> filteredTabPreviews( .toList(), ); } + +@Riverpod() +class AppLinksModeNotifier extends _$AppLinksModeNotifier { + final _service = GeckoEngineSettingsService(); + + Future setMode(AppLinksMode mode) async { + await _service.setAppLinksMode(mode); + ref.invalidateSelf(); + } + + @override + Future build() { + return _service.getAppLinksMode(); + } +} diff --git a/app/lib/features/geckoview/features/browser/domain/providers.g.dart b/app/lib/features/geckoview/features/browser/domain/providers.g.dart index 95a7e75c..c914398f 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -791,3 +791,48 @@ final class FilteredTabPreviewsFamily extends $Family @override String toString() => r'filteredTabPreviewsProvider'; } + +@ProviderFor(AppLinksModeNotifier) +final appLinksModeProvider = AppLinksModeNotifierProvider._(); + +final class AppLinksModeNotifierProvider + extends $AsyncNotifierProvider { + AppLinksModeNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'appLinksModeProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$appLinksModeNotifierHash(); + + @$internal + @override + AppLinksModeNotifier create() => AppLinksModeNotifier(); +} + +String _$appLinksModeNotifierHash() => + r'2643b7d2799870fd444f7db204ea452d975368a3'; + +abstract class _$AppLinksModeNotifier extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, AppLinksMode>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, AppLinksMode>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart index d5e1fbd7..864c7e54 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart @@ -30,7 +30,10 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart part 'engine_settings_replication.g.dart'; /// Checks if any Custom ETP setting changed between two EngineSettings instances. -bool _customEtpSettingsChanged(GeckoEngineSettings? previous, GeckoEngineSettings current) { +bool _customEtpSettingsChanged( + GeckoEngineSettings? previous, + GeckoEngineSettings current, +) { if (previous == null) return true; return previous.blockCookies != current.blockCookies || previous.customCookiePolicy != current.customCookiePolicy || @@ -39,8 +42,10 @@ bool _customEtpSettingsChanged(GeckoEngineSettings? previous, GeckoEngineSetting previous.blockCryptominers != current.blockCryptominers || previous.blockFingerprinters != current.blockFingerprinters || previous.blockRedirectTrackers != current.blockRedirectTrackers || - previous.blockSuspectedFingerprinters != current.blockSuspectedFingerprinters || - previous.suspectedFingerprintersScope != current.suspectedFingerprintersScope || + previous.blockSuspectedFingerprinters != + current.blockSuspectedFingerprinters || + previous.suspectedFingerprintersScope != + current.suspectedFingerprintersScope || previous.allowListBaseline != current.allowListBaseline || previous.allowListConvenience != current.allowListConvenience; } @@ -107,14 +112,18 @@ class EngineSettingsReplicationService await _service.javascriptEnabled(settings.javascriptEnabled); } // Check if tracking protection policy mode changed OR any custom ETP setting changed - final policyModeChanged = previous.value?.trackingProtectionPolicy != + final policyModeChanged = + previous.value?.trackingProtectionPolicy != settings.trackingProtectionPolicy; - final customSettingsChanged = settings.trackingProtectionPolicy == TrackingProtectionPolicy.custom && + final customSettingsChanged = + settings.trackingProtectionPolicy == + TrackingProtectionPolicy.custom && _customEtpSettingsChanged(previous.value, settings); if (policyModeChanged || customSettingsChanged) { // Always send full custom settings when in CUSTOM mode - if (settings.trackingProtectionPolicy == TrackingProtectionPolicy.custom) { + if (settings.trackingProtectionPolicy == + TrackingProtectionPolicy.custom) { await _service.customTrackingProtectionPolicy( trackingProtectionPolicy: settings.trackingProtectionPolicy, blockCookies: settings.blockCookies, @@ -124,8 +133,10 @@ class EngineSettingsReplicationService blockCryptominers: settings.blockCryptominers, blockFingerprinters: settings.blockFingerprinters, blockRedirectTrackers: settings.blockRedirectTrackers, - blockSuspectedFingerprinters: settings.blockSuspectedFingerprinters, - suspectedFingerprintersScope: settings.suspectedFingerprintersScope, + blockSuspectedFingerprinters: + settings.blockSuspectedFingerprinters, + suspectedFingerprintersScope: + settings.suspectedFingerprintersScope, allowListBaseline: settings.allowListBaseline, allowListConvenience: settings.allowListConvenience, ); diff --git a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart index e70b0520..d194ab20 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart @@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider } String _$engineSettingsReplicationServiceHash() => - r'0a9539e19946028f525de64ed5f33e2483e7abc2'; + r'3e06f0c273090f9f9dfc42f2140d60ef9557f176'; abstract class _$EngineSettingsReplicationService extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart b/app/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart index a058a8f9..21c8aa00 100644 --- a/app/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart +++ b/app/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart @@ -48,4 +48,4 @@ final class ShowSiteSettingsBadgeProvider } String _$showSiteSettingsBadgeHash() => - r'8b759a67e5c8b0f49a5d38b793a3357576a2eb87'; + r'e326617d2d794844c6bbe6d909042b91d95c2996'; diff --git a/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart b/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart index 0c0d7e4e..f5b05f0e 100644 --- a/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart +++ b/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart @@ -20,9 +20,11 @@ import 'package:fading_scroll/fading_scroll.dart'; import 'package:flutter/material.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; +import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/routing/routes.dart'; +import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/settings/presentation/widgets/sections.dart'; import 'package:weblibre/features/user/data/models/general_settings.dart'; @@ -65,6 +67,7 @@ class _TabCreationSection extends StatelessWidget { SettingSection(name: 'Tab Creation'), _NewTabDefaultSection(), _ExternalLinkHandlingSection(), + _AppLinksModeSection(), ], ); } @@ -340,3 +343,63 @@ class _TabBarSwipeBehaviorSection extends HookConsumerWidget { ); } } + +class _AppLinksModeSection extends HookConsumerWidget { + const _AppLinksModeSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final appLinksMode = ref.watch( + appLinksModeProvider.select((value) => value.value), + ); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ListTile( + title: Text('Open Links in Apps'), + subtitle: Text( + 'Choose how links that can be opened in other apps are handled', + ), + leading: Icon(MdiIcons.openInApp), + contentPadding: EdgeInsets.zero, + ), + RadioGroup( + groupValue: appLinksMode, + onChanged: (value) async { + if (value != null) { + await ref.read(appLinksModeProvider.notifier).setMode(value); + } + }, + child: const Column( + children: [ + RadioListTile.adaptive( + value: AppLinksMode.always, + title: Text('Always'), + subtitle: Text( + 'Always open links in their native apps without asking', + ), + ), + RadioListTile.adaptive( + value: AppLinksMode.ask, + title: Text('Ask before opening'), + subtitle: Text('Show a prompt before opening links in apps'), + ), + RadioListTile.adaptive( + value: AppLinksMode.never, + title: Text('Never'), + subtitle: Text( + 'Always open links in the browser instead of apps', + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/BaseBrowserFragment.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/BaseBrowserFragment.kt index 1429aa9e..12b76110 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/BaseBrowserFragment.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/BaseBrowserFragment.kt @@ -19,7 +19,9 @@ import android.widget.FrameLayout import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.CallSuper +import androidx.core.content.edit import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager import eu.weblibre.flutter_mozilla_components.addons.WebExtensionActionPopupActivity import eu.weblibre.flutter_mozilla_components.addons.WebExtensionPromptFeature import eu.weblibre.flutter_mozilla_components.databinding.FragmentBrowserBinding @@ -297,8 +299,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit sessionId = sessionId, fragmentManager = parentFragmentManager, loadUrlUseCase = components.useCases.sessionUseCases.loadUrl, - launchInApp = { true }, - shouldPrompt = { true }, + launchInApp = { GlobalComponents.shouldOpenLinksInApp() }, + shouldPrompt = { GlobalComponents.shouldPromptOpenLinksInApp() }, + alwaysOpenCheckboxAction = { + GlobalComponents.engineSettingsApi?.setAppLinksMode( + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ALWAYS + ) + }, failedToLaunchAction = { fallbackUrl -> fallbackUrl?.let { val appLinksUseCases = components.useCases.appLinksUseCases diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt index a2c9975a..44e4bce7 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt @@ -17,6 +17,7 @@ import eu.weblibre.flutter_mozilla_components.pigeons.GeckoTabContentEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoViewportEvents import eu.weblibre.flutter_mozilla_components.pigeons.ReaderViewController import eu.weblibre.flutter_mozilla_components.api.GeckoViewportApiImpl +import eu.weblibre.flutter_mozilla_components.api.GeckoEngineSettingsApiImpl import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -53,6 +54,25 @@ object GlobalComponents { // Viewport API for applying pending settings when engineView becomes available var viewportApi: GeckoViewportApiImpl? = null + // Engine settings API for managing engine-specific settings + var engineSettingsApi: GeckoEngineSettingsApiImpl? = null + + fun shouldOpenLinksInApp(): Boolean { + return when (engineSettingsApi!!.getAppLinksMode()) { + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ALWAYS -> true + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ASK -> true + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.NEVER -> false + } + } + + fun shouldPromptOpenLinksInApp(): Boolean { + return when (engineSettingsApi!!.getAppLinksMode()) { + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ALWAYS -> false + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ASK -> true + eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.NEVER -> false + } + } + @DelicateCoroutinesApi private fun restoreBrowserState(newComponents: Components) = GlobalScope.launch(Dispatchers.Main) { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt index 391e87f3..24ee8fe5 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt @@ -231,10 +231,12 @@ class GeckoBrowserApiImpl : GeckoBrowserApi { addonCollection ) + val engineSettingsApiImpl = GeckoEngineSettingsApiImpl() GeckoEngineSettingsApi.setUp( _flutterPluginBinding.binaryMessenger, - GeckoEngineSettingsApiImpl() + engineSettingsApiImpl ) + GlobalComponents.engineSettingsApi = engineSettingsApiImpl GeckoAddonsApi.setUp( _flutterPluginBinding.binaryMessenger, GeckoAddonsApiImpl(profileApplicationContext) diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt index d855615f..35a40385 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt @@ -6,7 +6,11 @@ package eu.weblibre.flutter_mozilla_components.api +import androidx.core.content.edit +import androidx.preference.PreferenceManager import eu.weblibre.flutter_mozilla_components.GlobalComponents +import eu.weblibre.flutter_mozilla_components.R +import eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode import eu.weblibre.flutter_mozilla_components.pigeons.ColorScheme import eu.weblibre.flutter_mozilla_components.pigeons.CookieBannerHandlingMode import eu.weblibre.flutter_mozilla_components.pigeons.CustomCookiePolicy @@ -317,4 +321,33 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi { override fun setPullToRefreshEnabled(enabled: Boolean) { GlobalComponents.pullToRefreshEnabled = enabled } + + override fun setAppLinksMode(mode: AppLinksMode) { + val context = components.profileApplicationContext + val prefKey = context.getString(R.string.pref_key_open_links_in_apps) + val modeValue = when (mode) { + AppLinksMode.ALWAYS -> context.getString(R.string.pref_key_open_links_in_apps_always) + AppLinksMode.ASK -> context.getString(R.string.pref_key_open_links_in_apps_ask) + AppLinksMode.NEVER -> context.getString(R.string.pref_key_open_links_in_apps_never) + } + + PreferenceManager.getDefaultSharedPreferences(context).edit { + putString(prefKey, modeValue) + } + } + + override fun getAppLinksMode(): AppLinksMode { + val context = components.profileApplicationContext + val prefKey = context.getString(R.string.pref_key_open_links_in_apps) + val defaultValue = context.getString(R.string.pref_key_open_links_in_apps_ask) + val modeValue = PreferenceManager.getDefaultSharedPreferences(context) + .getString(prefKey, defaultValue) ?: defaultValue + + return when (modeValue) { + context.getString(R.string.pref_key_open_links_in_apps_always) -> AppLinksMode.ALWAYS + context.getString(R.string.pref_key_open_links_in_apps_ask) -> AppLinksMode.ASK + context.getString(R.string.pref_key_open_links_in_apps_never) -> AppLinksMode.NEVER + else -> AppLinksMode.ASK + } + } } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Services.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Services.kt index 72c1b489..b4efdbb7 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Services.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Services.kt @@ -6,6 +6,7 @@ package eu.weblibre.flutter_mozilla_components.components import android.content.Context import androidx.preference.PreferenceManager +import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.R import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey import mozilla.components.browser.state.store.BrowserStore @@ -25,7 +26,7 @@ class Services( val appLinksInterceptor by lazy { AppLinksInterceptor( context = context, - launchInApp = { true }, + launchInApp = { GlobalComponents.shouldOpenLinksInApp() }, store = store, ) } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt index 7f185a17..ca129587 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -322,6 +322,22 @@ enum class CookieBannerHandlingMode(val raw: Int) { } } +/** App links behavior mode - controls how external app links are handled */ +enum class AppLinksMode(val raw: Int) { + /** Always open links in their native apps without prompting */ + ALWAYS(0), + /** Prompt user before opening in app (with "Always open" checkbox) */ + ASK(1), + /** Never open links in external apps, always use browser */ + NEVER(2); + + companion object { + fun ofRaw(raw: Int): AppLinksMode? { + return values().firstOrNull { it.raw == raw } + } + } +} + enum class WebContentIsolationStrategy(val raw: Int) { ISOLATE_NOTHING(0), ISOLATE_EVERYTHING(1), @@ -3036,345 +3052,350 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { } 144.toByte() -> { return (readValue(buffer) as Long?)?.let { - WebContentIsolationStrategy.ofRaw(it.toInt()) + AppLinksMode.ofRaw(it.toInt()) } } 145.toByte() -> { return (readValue(buffer) as Long?)?.let { - CustomCookiePolicy.ofRaw(it.toInt()) + WebContentIsolationStrategy.ofRaw(it.toInt()) } } 146.toByte() -> { return (readValue(buffer) as Long?)?.let { - TrackingScope.ofRaw(it.toInt()) + CustomCookiePolicy.ofRaw(it.toInt()) } } 147.toByte() -> { return (readValue(buffer) as Long?)?.let { - DohSettingsMode.ofRaw(it.toInt()) + TrackingScope.ofRaw(it.toInt()) } } 148.toByte() -> { return (readValue(buffer) as Long?)?.let { - DownloadStatus.ofRaw(it.toInt()) + DohSettingsMode.ofRaw(it.toInt()) } } 149.toByte() -> { return (readValue(buffer) as Long?)?.let { - LogLevel.ofRaw(it.toInt()) + DownloadStatus.ofRaw(it.toInt()) } } 150.toByte() -> { return (readValue(buffer) as Long?)?.let { - MlProgressType.ofRaw(it.toInt()) + LogLevel.ofRaw(it.toInt()) } } 151.toByte() -> { return (readValue(buffer) as Long?)?.let { - MlProgressStatus.ofRaw(it.toInt()) + MlProgressType.ofRaw(it.toInt()) } } 152.toByte() -> { return (readValue(buffer) as Long?)?.let { - ClearDataType.ofRaw(it.toInt()) + MlProgressStatus.ofRaw(it.toInt()) } } 153.toByte() -> { return (readValue(buffer) as Long?)?.let { - GeckoFetchMethod.ofRaw(it.toInt()) + ClearDataType.ofRaw(it.toInt()) } } 154.toByte() -> { return (readValue(buffer) as Long?)?.let { - GeckoFetchRedircet.ofRaw(it.toInt()) + GeckoFetchMethod.ofRaw(it.toInt()) } } 155.toByte() -> { return (readValue(buffer) as Long?)?.let { - GeckoFetchCookiePolicy.ofRaw(it.toInt()) + GeckoFetchRedircet.ofRaw(it.toInt()) } } 156.toByte() -> { return (readValue(buffer) as Long?)?.let { - BookmarkNodeType.ofRaw(it.toInt()) + GeckoFetchCookiePolicy.ofRaw(it.toInt()) } } 157.toByte() -> { return (readValue(buffer) as Long?)?.let { - SitePermissionStatus.ofRaw(it.toInt()) + BookmarkNodeType.ofRaw(it.toInt()) } } 158.toByte() -> { return (readValue(buffer) as Long?)?.let { - AutoplayStatus.ofRaw(it.toInt()) + SitePermissionStatus.ofRaw(it.toInt()) } } 159.toByte() -> { - return (readValue(buffer) as? List)?.let { - TranslationOptions.fromList(it) + return (readValue(buffer) as Long?)?.let { + AutoplayStatus.ofRaw(it.toInt()) } } 160.toByte() -> { return (readValue(buffer) as? List)?.let { - ReaderState.fromList(it) + TranslationOptions.fromList(it) } } 161.toByte() -> { return (readValue(buffer) as? List)?.let { - AddTabParams.fromList(it) + ReaderState.fromList(it) } } 162.toByte() -> { return (readValue(buffer) as? List)?.let { - LastMediaAccessState.fromList(it) + AddTabParams.fromList(it) } } 163.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryMetadataKey.fromList(it) + LastMediaAccessState.fromList(it) } } 164.toByte() -> { return (readValue(buffer) as? List)?.let { - PackageCategoryValue.fromList(it) + HistoryMetadataKey.fromList(it) } } 165.toByte() -> { return (readValue(buffer) as? List)?.let { - ExternalPackage.fromList(it) + PackageCategoryValue.fromList(it) } } 166.toByte() -> { return (readValue(buffer) as? List)?.let { - LoadUrlFlagsValue.fromList(it) + ExternalPackage.fromList(it) } } 167.toByte() -> { return (readValue(buffer) as? List)?.let { - SourceValue.fromList(it) + LoadUrlFlagsValue.fromList(it) } } 168.toByte() -> { return (readValue(buffer) as? List)?.let { - TabState.fromList(it) + SourceValue.fromList(it) } } 169.toByte() -> { return (readValue(buffer) as? List)?.let { - RecoverableTab.fromList(it) + TabState.fromList(it) } } 170.toByte() -> { return (readValue(buffer) as? List)?.let { - RecoverableBrowserState.fromList(it) + RecoverableTab.fromList(it) } } 171.toByte() -> { return (readValue(buffer) as? List)?.let { - IconRequest.fromList(it) + RecoverableBrowserState.fromList(it) } } 172.toByte() -> { return (readValue(buffer) as? List)?.let { - ResourceSize.fromList(it) + IconRequest.fromList(it) } } 173.toByte() -> { return (readValue(buffer) as? List)?.let { - Resource.fromList(it) + ResourceSize.fromList(it) } } 174.toByte() -> { return (readValue(buffer) as? List)?.let { - IconResult.fromList(it) + Resource.fromList(it) } } 175.toByte() -> { return (readValue(buffer) as? List)?.let { - CookiePartitionKey.fromList(it) + IconResult.fromList(it) } } 176.toByte() -> { return (readValue(buffer) as? List)?.let { - Cookie.fromList(it) + CookiePartitionKey.fromList(it) } } 177.toByte() -> { return (readValue(buffer) as? List)?.let { - VisitInfo.fromList(it) + Cookie.fromList(it) } } 178.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryItem.fromList(it) + VisitInfo.fromList(it) } } 179.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryState.fromList(it) + HistoryItem.fromList(it) } } 180.toByte() -> { return (readValue(buffer) as? List)?.let { - ReaderableState.fromList(it) + HistoryState.fromList(it) } } 181.toByte() -> { return (readValue(buffer) as? List)?.let { - SecurityInfoState.fromList(it) + ReaderableState.fromList(it) } } 182.toByte() -> { return (readValue(buffer) as? List)?.let { - TabContentState.fromList(it) + SecurityInfoState.fromList(it) } } 183.toByte() -> { return (readValue(buffer) as? List)?.let { - FindResultState.fromList(it) + TabContentState.fromList(it) } } 184.toByte() -> { return (readValue(buffer) as? List)?.let { - CustomSelectionAction.fromList(it) + FindResultState.fromList(it) } } 185.toByte() -> { return (readValue(buffer) as? List)?.let { - WebExtensionData.fromList(it) + CustomSelectionAction.fromList(it) } } 186.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoSuggestion.fromList(it) + WebExtensionData.fromList(it) } } 187.toByte() -> { return (readValue(buffer) as? List)?.let { - TabContent.fromList(it) + GeckoSuggestion.fromList(it) } } 188.toByte() -> { return (readValue(buffer) as? List)?.let { - ContentBlocking.fromList(it) + TabContent.fromList(it) } } 189.toByte() -> { return (readValue(buffer) as? List)?.let { - DohSettings.fromList(it) + ContentBlocking.fromList(it) } } 190.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoEngineSettings.fromList(it) + DohSettings.fromList(it) } } 191.toByte() -> { return (readValue(buffer) as? List)?.let { - AutocompleteResult.fromList(it) + GeckoEngineSettings.fromList(it) } } 192.toByte() -> { return (readValue(buffer) as? List)?.let { - UnknownHitResult.fromList(it) + AutocompleteResult.fromList(it) } } 193.toByte() -> { return (readValue(buffer) as? List)?.let { - ImageHitResult.fromList(it) + UnknownHitResult.fromList(it) } } 194.toByte() -> { return (readValue(buffer) as? List)?.let { - VideoHitResult.fromList(it) + ImageHitResult.fromList(it) } } 195.toByte() -> { return (readValue(buffer) as? List)?.let { - AudioHitResult.fromList(it) + VideoHitResult.fromList(it) } } 196.toByte() -> { return (readValue(buffer) as? List)?.let { - ImageSrcHitResult.fromList(it) + AudioHitResult.fromList(it) } } 197.toByte() -> { return (readValue(buffer) as? List)?.let { - PhoneHitResult.fromList(it) + ImageSrcHitResult.fromList(it) } } 198.toByte() -> { return (readValue(buffer) as? List)?.let { - EmailHitResult.fromList(it) + PhoneHitResult.fromList(it) } } 199.toByte() -> { return (readValue(buffer) as? List)?.let { - GeoHitResult.fromList(it) + EmailHitResult.fromList(it) } } 200.toByte() -> { return (readValue(buffer) as? List)?.let { - DownloadState.fromList(it) + GeoHitResult.fromList(it) } } 201.toByte() -> { return (readValue(buffer) as? List)?.let { - ShareInternetResourceState.fromList(it) + DownloadState.fromList(it) } } 202.toByte() -> { return (readValue(buffer) as? List)?.let { - AddonCollection.fromList(it) + ShareInternetResourceState.fromList(it) } } 203.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoPref.fromList(it) + AddonCollection.fromList(it) } } 204.toByte() -> { return (readValue(buffer) as? List)?.let { - MlProgressData.fromList(it) + GeckoPref.fromList(it) } } 205.toByte() -> { return (readValue(buffer) as? List)?.let { - ContainerSiteAssignment.fromList(it) + MlProgressData.fromList(it) } } 206.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoHeader.fromList(it) + ContainerSiteAssignment.fromList(it) } } 207.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoFetchRequest.fromList(it) + GeckoHeader.fromList(it) } } 208.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoFetchResponse.fromList(it) + GeckoFetchRequest.fromList(it) } } 209.toByte() -> { return (readValue(buffer) as? List)?.let { - BookmarkNode.fromList(it) + GeckoFetchResponse.fromList(it) } } 210.toByte() -> { return (readValue(buffer) as? List)?.let { - BookmarkInfo.fromList(it) + BookmarkNode.fromList(it) } } 211.toByte() -> { return (readValue(buffer) as? List)?.let { - SitePermissions.fromList(it) + BookmarkInfo.fromList(it) } } 212.toByte() -> { + return (readValue(buffer) as? List)?.let { + SitePermissions.fromList(it) + } + } + 213.toByte() -> { return (readValue(buffer) as? List)?.let { TrackingProtectionException.fromList(it) } @@ -3444,282 +3465,286 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { stream.write(143) writeValue(stream, value.raw.toLong()) } - is WebContentIsolationStrategy -> { + is AppLinksMode -> { stream.write(144) writeValue(stream, value.raw.toLong()) } - is CustomCookiePolicy -> { + is WebContentIsolationStrategy -> { stream.write(145) writeValue(stream, value.raw.toLong()) } - is TrackingScope -> { + is CustomCookiePolicy -> { stream.write(146) writeValue(stream, value.raw.toLong()) } - is DohSettingsMode -> { + is TrackingScope -> { stream.write(147) writeValue(stream, value.raw.toLong()) } - is DownloadStatus -> { + is DohSettingsMode -> { stream.write(148) writeValue(stream, value.raw.toLong()) } - is LogLevel -> { + is DownloadStatus -> { stream.write(149) writeValue(stream, value.raw.toLong()) } - is MlProgressType -> { + is LogLevel -> { stream.write(150) writeValue(stream, value.raw.toLong()) } - is MlProgressStatus -> { + is MlProgressType -> { stream.write(151) writeValue(stream, value.raw.toLong()) } - is ClearDataType -> { + is MlProgressStatus -> { stream.write(152) writeValue(stream, value.raw.toLong()) } - is GeckoFetchMethod -> { + is ClearDataType -> { stream.write(153) writeValue(stream, value.raw.toLong()) } - is GeckoFetchRedircet -> { + is GeckoFetchMethod -> { stream.write(154) writeValue(stream, value.raw.toLong()) } - is GeckoFetchCookiePolicy -> { + is GeckoFetchRedircet -> { stream.write(155) writeValue(stream, value.raw.toLong()) } - is BookmarkNodeType -> { + is GeckoFetchCookiePolicy -> { stream.write(156) writeValue(stream, value.raw.toLong()) } - is SitePermissionStatus -> { + is BookmarkNodeType -> { stream.write(157) writeValue(stream, value.raw.toLong()) } - is AutoplayStatus -> { + is SitePermissionStatus -> { stream.write(158) writeValue(stream, value.raw.toLong()) } - is TranslationOptions -> { + is AutoplayStatus -> { stream.write(159) - writeValue(stream, value.toList()) + writeValue(stream, value.raw.toLong()) } - is ReaderState -> { + is TranslationOptions -> { stream.write(160) writeValue(stream, value.toList()) } - is AddTabParams -> { + is ReaderState -> { stream.write(161) writeValue(stream, value.toList()) } - is LastMediaAccessState -> { + is AddTabParams -> { stream.write(162) writeValue(stream, value.toList()) } - is HistoryMetadataKey -> { + is LastMediaAccessState -> { stream.write(163) writeValue(stream, value.toList()) } - is PackageCategoryValue -> { + is HistoryMetadataKey -> { stream.write(164) writeValue(stream, value.toList()) } - is ExternalPackage -> { + is PackageCategoryValue -> { stream.write(165) writeValue(stream, value.toList()) } - is LoadUrlFlagsValue -> { + is ExternalPackage -> { stream.write(166) writeValue(stream, value.toList()) } - is SourceValue -> { + is LoadUrlFlagsValue -> { stream.write(167) writeValue(stream, value.toList()) } - is TabState -> { + is SourceValue -> { stream.write(168) writeValue(stream, value.toList()) } - is RecoverableTab -> { + is TabState -> { stream.write(169) writeValue(stream, value.toList()) } - is RecoverableBrowserState -> { + is RecoverableTab -> { stream.write(170) writeValue(stream, value.toList()) } - is IconRequest -> { + is RecoverableBrowserState -> { stream.write(171) writeValue(stream, value.toList()) } - is ResourceSize -> { + is IconRequest -> { stream.write(172) writeValue(stream, value.toList()) } - is Resource -> { + is ResourceSize -> { stream.write(173) writeValue(stream, value.toList()) } - is IconResult -> { + is Resource -> { stream.write(174) writeValue(stream, value.toList()) } - is CookiePartitionKey -> { + is IconResult -> { stream.write(175) writeValue(stream, value.toList()) } - is Cookie -> { + is CookiePartitionKey -> { stream.write(176) writeValue(stream, value.toList()) } - is VisitInfo -> { + is Cookie -> { stream.write(177) writeValue(stream, value.toList()) } - is HistoryItem -> { + is VisitInfo -> { stream.write(178) writeValue(stream, value.toList()) } - is HistoryState -> { + is HistoryItem -> { stream.write(179) writeValue(stream, value.toList()) } - is ReaderableState -> { + is HistoryState -> { stream.write(180) writeValue(stream, value.toList()) } - is SecurityInfoState -> { + is ReaderableState -> { stream.write(181) writeValue(stream, value.toList()) } - is TabContentState -> { + is SecurityInfoState -> { stream.write(182) writeValue(stream, value.toList()) } - is FindResultState -> { + is TabContentState -> { stream.write(183) writeValue(stream, value.toList()) } - is CustomSelectionAction -> { + is FindResultState -> { stream.write(184) writeValue(stream, value.toList()) } - is WebExtensionData -> { + is CustomSelectionAction -> { stream.write(185) writeValue(stream, value.toList()) } - is GeckoSuggestion -> { + is WebExtensionData -> { stream.write(186) writeValue(stream, value.toList()) } - is TabContent -> { + is GeckoSuggestion -> { stream.write(187) writeValue(stream, value.toList()) } - is ContentBlocking -> { + is TabContent -> { stream.write(188) writeValue(stream, value.toList()) } - is DohSettings -> { + is ContentBlocking -> { stream.write(189) writeValue(stream, value.toList()) } - is GeckoEngineSettings -> { + is DohSettings -> { stream.write(190) writeValue(stream, value.toList()) } - is AutocompleteResult -> { + is GeckoEngineSettings -> { stream.write(191) writeValue(stream, value.toList()) } - is UnknownHitResult -> { + is AutocompleteResult -> { stream.write(192) writeValue(stream, value.toList()) } - is ImageHitResult -> { + is UnknownHitResult -> { stream.write(193) writeValue(stream, value.toList()) } - is VideoHitResult -> { + is ImageHitResult -> { stream.write(194) writeValue(stream, value.toList()) } - is AudioHitResult -> { + is VideoHitResult -> { stream.write(195) writeValue(stream, value.toList()) } - is ImageSrcHitResult -> { + is AudioHitResult -> { stream.write(196) writeValue(stream, value.toList()) } - is PhoneHitResult -> { + is ImageSrcHitResult -> { stream.write(197) writeValue(stream, value.toList()) } - is EmailHitResult -> { + is PhoneHitResult -> { stream.write(198) writeValue(stream, value.toList()) } - is GeoHitResult -> { + is EmailHitResult -> { stream.write(199) writeValue(stream, value.toList()) } - is DownloadState -> { + is GeoHitResult -> { stream.write(200) writeValue(stream, value.toList()) } - is ShareInternetResourceState -> { + is DownloadState -> { stream.write(201) writeValue(stream, value.toList()) } - is AddonCollection -> { + is ShareInternetResourceState -> { stream.write(202) writeValue(stream, value.toList()) } - is GeckoPref -> { + is AddonCollection -> { stream.write(203) writeValue(stream, value.toList()) } - is MlProgressData -> { + is GeckoPref -> { stream.write(204) writeValue(stream, value.toList()) } - is ContainerSiteAssignment -> { + is MlProgressData -> { stream.write(205) writeValue(stream, value.toList()) } - is GeckoHeader -> { + is ContainerSiteAssignment -> { stream.write(206) writeValue(stream, value.toList()) } - is GeckoFetchRequest -> { + is GeckoHeader -> { stream.write(207) writeValue(stream, value.toList()) } - is GeckoFetchResponse -> { + is GeckoFetchRequest -> { stream.write(208) writeValue(stream, value.toList()) } - is BookmarkNode -> { + is GeckoFetchResponse -> { stream.write(209) writeValue(stream, value.toList()) } - is BookmarkInfo -> { + is BookmarkNode -> { stream.write(210) writeValue(stream, value.toList()) } - is SitePermissions -> { + is BookmarkInfo -> { stream.write(211) writeValue(stream, value.toList()) } - is TrackingProtectionException -> { + is SitePermissions -> { stream.write(212) writeValue(stream, value.toList()) } + is TrackingProtectionException -> { + stream.write(213) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -3819,6 +3844,12 @@ interface GeckoEngineSettingsApi { fun setDefaultSettings(settings: GeckoEngineSettings) fun updateRuntimeSettings(settings: GeckoEngineSettings) fun setPullToRefreshEnabled(enabled: Boolean) + /** + * Sets the app links mode preference (stored in SharedPreferences). + * Controls how external app links are handled in the browser. + */ + fun setAppLinksMode(mode: AppLinksMode) + fun getAppLinksMode(): AppLinksMode companion object { /** The codec used by GeckoEngineSettingsApi. */ @@ -3883,6 +3914,39 @@ interface GeckoEngineSettingsApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setAppLinksMode$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val modeArg = args[0] as AppLinksMode + val wrapped: List = try { + api.setAppLinksMode(modeArg) + listOf(null) + } catch (exception: Throwable) { + GeckoPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.getAppLinksMode$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getAppLinksMode()) + } catch (exception: Throwable) { + GeckoPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/packages/flutter_mozilla_components/android/src/main/res/values/preference_keys.xml b/packages/flutter_mozilla_components/android/src/main/res/values/preference_keys.xml index f7458897..407cd3da 100644 --- a/packages/flutter_mozilla_components/android/src/main/res/values/preference_keys.xml +++ b/packages/flutter_mozilla_components/android/src/main/res/values/preference_keys.xml @@ -28,4 +28,10 @@ pref_key_override_amo_collection pref_key_override_amo_user pref_key_compose_ui + + + pref_key_open_links_in_apps + pref_key_open_links_in_apps_always + pref_key_open_links_in_apps_ask + pref_key_open_links_in_apps_never diff --git a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart index 78d49cc2..cf3dfe80 100644 --- a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart +++ b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart @@ -36,6 +36,7 @@ export 'src/pigeons/gecko.g.dart' show AddTabParams, AddonCollection, + AppLinksMode, AudioHitResult, AutoplayStatus, BookmarkInfo, diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart index 1c67d8eb..5c2a5868 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart @@ -138,4 +138,14 @@ class GeckoEngineSettingsService { Future setPullToRefreshEnabled(bool enabled) { return _api.setPullToRefreshEnabled(enabled); } + + /// Sets the app links mode preference. + /// Controls how external app links are handled in browser. + Future setAppLinksMode(AppLinksMode mode) { + return _api.setAppLinksMode(mode); + } + + Future getAppLinksMode() { + return _api.getAppLinksMode(); + } } diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 89d3b5c9..fe387835 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -180,6 +180,16 @@ enum CookieBannerHandlingMode { rejectOrAcceptAll, } +/// App links behavior mode - controls how external app links are handled +enum AppLinksMode { + /// Always open links in their native apps without prompting + always, + /// Prompt user before opening in app (with "Always open" checkbox) + ask, + /// Never open links in external apps, always use browser + never, +} + enum WebContentIsolationStrategy { isolateNothing, isolateEverything, @@ -3805,213 +3815,216 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is CookieBannerHandlingMode) { buffer.putUint8(143); writeValue(buffer, value.index); - } else if (value is WebContentIsolationStrategy) { + } else if (value is AppLinksMode) { buffer.putUint8(144); writeValue(buffer, value.index); - } else if (value is CustomCookiePolicy) { + } else if (value is WebContentIsolationStrategy) { buffer.putUint8(145); writeValue(buffer, value.index); - } else if (value is TrackingScope) { + } else if (value is CustomCookiePolicy) { buffer.putUint8(146); writeValue(buffer, value.index); - } else if (value is DohSettingsMode) { + } else if (value is TrackingScope) { buffer.putUint8(147); writeValue(buffer, value.index); - } else if (value is DownloadStatus) { + } else if (value is DohSettingsMode) { buffer.putUint8(148); writeValue(buffer, value.index); - } else if (value is LogLevel) { + } else if (value is DownloadStatus) { buffer.putUint8(149); writeValue(buffer, value.index); - } else if (value is MlProgressType) { + } else if (value is LogLevel) { buffer.putUint8(150); writeValue(buffer, value.index); - } else if (value is MlProgressStatus) { + } else if (value is MlProgressType) { buffer.putUint8(151); writeValue(buffer, value.index); - } else if (value is ClearDataType) { + } else if (value is MlProgressStatus) { buffer.putUint8(152); writeValue(buffer, value.index); - } else if (value is GeckoFetchMethod) { + } else if (value is ClearDataType) { buffer.putUint8(153); writeValue(buffer, value.index); - } else if (value is GeckoFetchRedircet) { + } else if (value is GeckoFetchMethod) { buffer.putUint8(154); writeValue(buffer, value.index); - } else if (value is GeckoFetchCookiePolicy) { + } else if (value is GeckoFetchRedircet) { buffer.putUint8(155); writeValue(buffer, value.index); - } else if (value is BookmarkNodeType) { + } else if (value is GeckoFetchCookiePolicy) { buffer.putUint8(156); writeValue(buffer, value.index); - } else if (value is SitePermissionStatus) { + } else if (value is BookmarkNodeType) { buffer.putUint8(157); writeValue(buffer, value.index); - } else if (value is AutoplayStatus) { + } else if (value is SitePermissionStatus) { buffer.putUint8(158); writeValue(buffer, value.index); - } else if (value is TranslationOptions) { + } else if (value is AutoplayStatus) { buffer.putUint8(159); - writeValue(buffer, value.encode()); - } else if (value is ReaderState) { + writeValue(buffer, value.index); + } else if (value is TranslationOptions) { buffer.putUint8(160); writeValue(buffer, value.encode()); - } else if (value is AddTabParams) { + } else if (value is ReaderState) { buffer.putUint8(161); writeValue(buffer, value.encode()); - } else if (value is LastMediaAccessState) { + } else if (value is AddTabParams) { buffer.putUint8(162); writeValue(buffer, value.encode()); - } else if (value is HistoryMetadataKey) { + } else if (value is LastMediaAccessState) { buffer.putUint8(163); writeValue(buffer, value.encode()); - } else if (value is PackageCategoryValue) { + } else if (value is HistoryMetadataKey) { buffer.putUint8(164); writeValue(buffer, value.encode()); - } else if (value is ExternalPackage) { + } else if (value is PackageCategoryValue) { buffer.putUint8(165); writeValue(buffer, value.encode()); - } else if (value is LoadUrlFlagsValue) { + } else if (value is ExternalPackage) { buffer.putUint8(166); writeValue(buffer, value.encode()); - } else if (value is SourceValue) { + } else if (value is LoadUrlFlagsValue) { buffer.putUint8(167); writeValue(buffer, value.encode()); - } else if (value is TabState) { + } else if (value is SourceValue) { buffer.putUint8(168); writeValue(buffer, value.encode()); - } else if (value is RecoverableTab) { + } else if (value is TabState) { buffer.putUint8(169); writeValue(buffer, value.encode()); - } else if (value is RecoverableBrowserState) { + } else if (value is RecoverableTab) { buffer.putUint8(170); writeValue(buffer, value.encode()); - } else if (value is IconRequest) { + } else if (value is RecoverableBrowserState) { buffer.putUint8(171); writeValue(buffer, value.encode()); - } else if (value is ResourceSize) { + } else if (value is IconRequest) { buffer.putUint8(172); writeValue(buffer, value.encode()); - } else if (value is Resource) { + } else if (value is ResourceSize) { buffer.putUint8(173); writeValue(buffer, value.encode()); - } else if (value is IconResult) { + } else if (value is Resource) { buffer.putUint8(174); writeValue(buffer, value.encode()); - } else if (value is CookiePartitionKey) { + } else if (value is IconResult) { buffer.putUint8(175); writeValue(buffer, value.encode()); - } else if (value is Cookie) { + } else if (value is CookiePartitionKey) { buffer.putUint8(176); writeValue(buffer, value.encode()); - } else if (value is VisitInfo) { + } else if (value is Cookie) { buffer.putUint8(177); writeValue(buffer, value.encode()); - } else if (value is HistoryItem) { + } else if (value is VisitInfo) { buffer.putUint8(178); writeValue(buffer, value.encode()); - } else if (value is HistoryState) { + } else if (value is HistoryItem) { buffer.putUint8(179); writeValue(buffer, value.encode()); - } else if (value is ReaderableState) { + } else if (value is HistoryState) { buffer.putUint8(180); writeValue(buffer, value.encode()); - } else if (value is SecurityInfoState) { + } else if (value is ReaderableState) { buffer.putUint8(181); writeValue(buffer, value.encode()); - } else if (value is TabContentState) { + } else if (value is SecurityInfoState) { buffer.putUint8(182); writeValue(buffer, value.encode()); - } else if (value is FindResultState) { + } else if (value is TabContentState) { buffer.putUint8(183); writeValue(buffer, value.encode()); - } else if (value is CustomSelectionAction) { + } else if (value is FindResultState) { buffer.putUint8(184); writeValue(buffer, value.encode()); - } else if (value is WebExtensionData) { + } else if (value is CustomSelectionAction) { buffer.putUint8(185); writeValue(buffer, value.encode()); - } else if (value is GeckoSuggestion) { + } else if (value is WebExtensionData) { buffer.putUint8(186); writeValue(buffer, value.encode()); - } else if (value is TabContent) { + } else if (value is GeckoSuggestion) { buffer.putUint8(187); writeValue(buffer, value.encode()); - } else if (value is ContentBlocking) { + } else if (value is TabContent) { buffer.putUint8(188); writeValue(buffer, value.encode()); - } else if (value is DohSettings) { + } else if (value is ContentBlocking) { buffer.putUint8(189); writeValue(buffer, value.encode()); - } else if (value is GeckoEngineSettings) { + } else if (value is DohSettings) { buffer.putUint8(190); writeValue(buffer, value.encode()); - } else if (value is AutocompleteResult) { + } else if (value is GeckoEngineSettings) { buffer.putUint8(191); writeValue(buffer, value.encode()); - } else if (value is UnknownHitResult) { + } else if (value is AutocompleteResult) { buffer.putUint8(192); writeValue(buffer, value.encode()); - } else if (value is ImageHitResult) { + } else if (value is UnknownHitResult) { buffer.putUint8(193); writeValue(buffer, value.encode()); - } else if (value is VideoHitResult) { + } else if (value is ImageHitResult) { buffer.putUint8(194); writeValue(buffer, value.encode()); - } else if (value is AudioHitResult) { + } else if (value is VideoHitResult) { buffer.putUint8(195); writeValue(buffer, value.encode()); - } else if (value is ImageSrcHitResult) { + } else if (value is AudioHitResult) { buffer.putUint8(196); writeValue(buffer, value.encode()); - } else if (value is PhoneHitResult) { + } else if (value is ImageSrcHitResult) { buffer.putUint8(197); writeValue(buffer, value.encode()); - } else if (value is EmailHitResult) { + } else if (value is PhoneHitResult) { buffer.putUint8(198); writeValue(buffer, value.encode()); - } else if (value is GeoHitResult) { + } else if (value is EmailHitResult) { buffer.putUint8(199); writeValue(buffer, value.encode()); - } else if (value is DownloadState) { + } else if (value is GeoHitResult) { buffer.putUint8(200); writeValue(buffer, value.encode()); - } else if (value is ShareInternetResourceState) { + } else if (value is DownloadState) { buffer.putUint8(201); writeValue(buffer, value.encode()); - } else if (value is AddonCollection) { + } else if (value is ShareInternetResourceState) { buffer.putUint8(202); writeValue(buffer, value.encode()); - } else if (value is GeckoPref) { + } else if (value is AddonCollection) { buffer.putUint8(203); writeValue(buffer, value.encode()); - } else if (value is MlProgressData) { + } else if (value is GeckoPref) { buffer.putUint8(204); writeValue(buffer, value.encode()); - } else if (value is ContainerSiteAssignment) { + } else if (value is MlProgressData) { buffer.putUint8(205); writeValue(buffer, value.encode()); - } else if (value is GeckoHeader) { + } else if (value is ContainerSiteAssignment) { buffer.putUint8(206); writeValue(buffer, value.encode()); - } else if (value is GeckoFetchRequest) { + } else if (value is GeckoHeader) { buffer.putUint8(207); writeValue(buffer, value.encode()); - } else if (value is GeckoFetchResponse) { + } else if (value is GeckoFetchRequest) { buffer.putUint8(208); writeValue(buffer, value.encode()); - } else if (value is BookmarkNode) { + } else if (value is GeckoFetchResponse) { buffer.putUint8(209); writeValue(buffer, value.encode()); - } else if (value is BookmarkInfo) { + } else if (value is BookmarkNode) { buffer.putUint8(210); writeValue(buffer, value.encode()); - } else if (value is SitePermissions) { + } else if (value is BookmarkInfo) { buffer.putUint8(211); writeValue(buffer, value.encode()); - } else if (value is TrackingProtectionException) { + } else if (value is SitePermissions) { buffer.putUint8(212); writeValue(buffer, value.encode()); + } else if (value is TrackingProtectionException) { + buffer.putUint8(213); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -4067,156 +4080,159 @@ class _PigeonCodec extends StandardMessageCodec { return value == null ? null : CookieBannerHandlingMode.values[value]; case 144: final value = readValue(buffer) as int?; - return value == null ? null : WebContentIsolationStrategy.values[value]; + return value == null ? null : AppLinksMode.values[value]; case 145: final value = readValue(buffer) as int?; - return value == null ? null : CustomCookiePolicy.values[value]; + return value == null ? null : WebContentIsolationStrategy.values[value]; case 146: final value = readValue(buffer) as int?; - return value == null ? null : TrackingScope.values[value]; + return value == null ? null : CustomCookiePolicy.values[value]; case 147: final value = readValue(buffer) as int?; - return value == null ? null : DohSettingsMode.values[value]; + return value == null ? null : TrackingScope.values[value]; case 148: final value = readValue(buffer) as int?; - return value == null ? null : DownloadStatus.values[value]; + return value == null ? null : DohSettingsMode.values[value]; case 149: final value = readValue(buffer) as int?; - return value == null ? null : LogLevel.values[value]; + return value == null ? null : DownloadStatus.values[value]; case 150: final value = readValue(buffer) as int?; - return value == null ? null : MlProgressType.values[value]; + return value == null ? null : LogLevel.values[value]; case 151: final value = readValue(buffer) as int?; - return value == null ? null : MlProgressStatus.values[value]; + return value == null ? null : MlProgressType.values[value]; case 152: final value = readValue(buffer) as int?; - return value == null ? null : ClearDataType.values[value]; + return value == null ? null : MlProgressStatus.values[value]; case 153: final value = readValue(buffer) as int?; - return value == null ? null : GeckoFetchMethod.values[value]; + return value == null ? null : ClearDataType.values[value]; case 154: final value = readValue(buffer) as int?; - return value == null ? null : GeckoFetchRedircet.values[value]; + return value == null ? null : GeckoFetchMethod.values[value]; case 155: final value = readValue(buffer) as int?; - return value == null ? null : GeckoFetchCookiePolicy.values[value]; + return value == null ? null : GeckoFetchRedircet.values[value]; case 156: final value = readValue(buffer) as int?; - return value == null ? null : BookmarkNodeType.values[value]; + return value == null ? null : GeckoFetchCookiePolicy.values[value]; case 157: final value = readValue(buffer) as int?; - return value == null ? null : SitePermissionStatus.values[value]; + return value == null ? null : BookmarkNodeType.values[value]; case 158: final value = readValue(buffer) as int?; - return value == null ? null : AutoplayStatus.values[value]; + return value == null ? null : SitePermissionStatus.values[value]; case 159: - return TranslationOptions.decode(readValue(buffer)!); + final value = readValue(buffer) as int?; + return value == null ? null : AutoplayStatus.values[value]; case 160: - return ReaderState.decode(readValue(buffer)!); + return TranslationOptions.decode(readValue(buffer)!); case 161: - return AddTabParams.decode(readValue(buffer)!); + return ReaderState.decode(readValue(buffer)!); case 162: - return LastMediaAccessState.decode(readValue(buffer)!); + return AddTabParams.decode(readValue(buffer)!); case 163: - return HistoryMetadataKey.decode(readValue(buffer)!); + return LastMediaAccessState.decode(readValue(buffer)!); case 164: - return PackageCategoryValue.decode(readValue(buffer)!); + return HistoryMetadataKey.decode(readValue(buffer)!); case 165: - return ExternalPackage.decode(readValue(buffer)!); + return PackageCategoryValue.decode(readValue(buffer)!); case 166: - return LoadUrlFlagsValue.decode(readValue(buffer)!); + return ExternalPackage.decode(readValue(buffer)!); case 167: - return SourceValue.decode(readValue(buffer)!); + return LoadUrlFlagsValue.decode(readValue(buffer)!); case 168: - return TabState.decode(readValue(buffer)!); + return SourceValue.decode(readValue(buffer)!); case 169: - return RecoverableTab.decode(readValue(buffer)!); + return TabState.decode(readValue(buffer)!); case 170: - return RecoverableBrowserState.decode(readValue(buffer)!); + return RecoverableTab.decode(readValue(buffer)!); case 171: - return IconRequest.decode(readValue(buffer)!); + return RecoverableBrowserState.decode(readValue(buffer)!); case 172: - return ResourceSize.decode(readValue(buffer)!); + return IconRequest.decode(readValue(buffer)!); case 173: - return Resource.decode(readValue(buffer)!); + return ResourceSize.decode(readValue(buffer)!); case 174: - return IconResult.decode(readValue(buffer)!); + return Resource.decode(readValue(buffer)!); case 175: - return CookiePartitionKey.decode(readValue(buffer)!); + return IconResult.decode(readValue(buffer)!); case 176: - return Cookie.decode(readValue(buffer)!); + return CookiePartitionKey.decode(readValue(buffer)!); case 177: - return VisitInfo.decode(readValue(buffer)!); + return Cookie.decode(readValue(buffer)!); case 178: - return HistoryItem.decode(readValue(buffer)!); + return VisitInfo.decode(readValue(buffer)!); case 179: - return HistoryState.decode(readValue(buffer)!); + return HistoryItem.decode(readValue(buffer)!); case 180: - return ReaderableState.decode(readValue(buffer)!); + return HistoryState.decode(readValue(buffer)!); case 181: - return SecurityInfoState.decode(readValue(buffer)!); + return ReaderableState.decode(readValue(buffer)!); case 182: - return TabContentState.decode(readValue(buffer)!); + return SecurityInfoState.decode(readValue(buffer)!); case 183: - return FindResultState.decode(readValue(buffer)!); + return TabContentState.decode(readValue(buffer)!); case 184: - return CustomSelectionAction.decode(readValue(buffer)!); + return FindResultState.decode(readValue(buffer)!); case 185: - return WebExtensionData.decode(readValue(buffer)!); + return CustomSelectionAction.decode(readValue(buffer)!); case 186: - return GeckoSuggestion.decode(readValue(buffer)!); + return WebExtensionData.decode(readValue(buffer)!); case 187: - return TabContent.decode(readValue(buffer)!); + return GeckoSuggestion.decode(readValue(buffer)!); case 188: - return ContentBlocking.decode(readValue(buffer)!); + return TabContent.decode(readValue(buffer)!); case 189: - return DohSettings.decode(readValue(buffer)!); + return ContentBlocking.decode(readValue(buffer)!); case 190: - return GeckoEngineSettings.decode(readValue(buffer)!); + return DohSettings.decode(readValue(buffer)!); case 191: - return AutocompleteResult.decode(readValue(buffer)!); + return GeckoEngineSettings.decode(readValue(buffer)!); case 192: - return UnknownHitResult.decode(readValue(buffer)!); + return AutocompleteResult.decode(readValue(buffer)!); case 193: - return ImageHitResult.decode(readValue(buffer)!); + return UnknownHitResult.decode(readValue(buffer)!); case 194: - return VideoHitResult.decode(readValue(buffer)!); + return ImageHitResult.decode(readValue(buffer)!); case 195: - return AudioHitResult.decode(readValue(buffer)!); + return VideoHitResult.decode(readValue(buffer)!); case 196: - return ImageSrcHitResult.decode(readValue(buffer)!); + return AudioHitResult.decode(readValue(buffer)!); case 197: - return PhoneHitResult.decode(readValue(buffer)!); + return ImageSrcHitResult.decode(readValue(buffer)!); case 198: - return EmailHitResult.decode(readValue(buffer)!); + return PhoneHitResult.decode(readValue(buffer)!); case 199: - return GeoHitResult.decode(readValue(buffer)!); + return EmailHitResult.decode(readValue(buffer)!); case 200: - return DownloadState.decode(readValue(buffer)!); + return GeoHitResult.decode(readValue(buffer)!); case 201: - return ShareInternetResourceState.decode(readValue(buffer)!); + return DownloadState.decode(readValue(buffer)!); case 202: - return AddonCollection.decode(readValue(buffer)!); + return ShareInternetResourceState.decode(readValue(buffer)!); case 203: - return GeckoPref.decode(readValue(buffer)!); + return AddonCollection.decode(readValue(buffer)!); case 204: - return MlProgressData.decode(readValue(buffer)!); + return GeckoPref.decode(readValue(buffer)!); case 205: - return ContainerSiteAssignment.decode(readValue(buffer)!); + return MlProgressData.decode(readValue(buffer)!); case 206: - return GeckoHeader.decode(readValue(buffer)!); + return ContainerSiteAssignment.decode(readValue(buffer)!); case 207: - return GeckoFetchRequest.decode(readValue(buffer)!); + return GeckoHeader.decode(readValue(buffer)!); case 208: - return GeckoFetchResponse.decode(readValue(buffer)!); + return GeckoFetchRequest.decode(readValue(buffer)!); case 209: - return BookmarkNode.decode(readValue(buffer)!); + return GeckoFetchResponse.decode(readValue(buffer)!); case 210: - return BookmarkInfo.decode(readValue(buffer)!); + return BookmarkNode.decode(readValue(buffer)!); case 211: - return SitePermissions.decode(readValue(buffer)!); + return BookmarkInfo.decode(readValue(buffer)!); case 212: + return SitePermissions.decode(readValue(buffer)!); + case 213: return TrackingProtectionException.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -4414,6 +4430,57 @@ class GeckoEngineSettingsApi { return; } } + + /// Sets the app links mode preference (stored in SharedPreferences). + /// Controls how external app links are handled in the browser. + Future setAppLinksMode(AppLinksMode mode) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setAppLinksMode$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([mode]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getAppLinksMode() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.getAppLinksMode$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as AppLinksMode?)!; + } + } } class GeckoSessionApi { diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index ebf6d6e8..c87e2caa 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -640,6 +640,18 @@ enum ColorScheme { system, light, dark } enum CookieBannerHandlingMode { disabled, rejectAll, rejectOrAcceptAll } +/// App links behavior mode - controls how external app links are handled +enum AppLinksMode { + /// Always open links in their native apps without prompting + always, + + /// Prompt user before opening in app (with "Always open" checkbox) + ask, + + /// Never open links in external apps, always use browser + never, +} + enum WebContentIsolationStrategy { isolateNothing, isolateEverything, @@ -994,6 +1006,12 @@ abstract class GeckoEngineSettingsApi { void setDefaultSettings(GeckoEngineSettings settings); void updateRuntimeSettings(GeckoEngineSettings settings); void setPullToRefreshEnabled(bool enabled); + + /// Sets the app links mode preference (stored in SharedPreferences). + /// Controls how external app links are handled in the browser. + void setAppLinksMode(AppLinksMode mode); + + AppLinksMode getAppLinksMode(); } @HostApi()