support for external download managers

This commit is contained in:
Fabian Freund
2026-02-04 07:46:50 +01:00
parent c8179c585e
commit 28907a62a7
15 changed files with 584 additions and 2 deletions
@@ -99,6 +99,23 @@ class EngineSettingsReplicationService
},
);
ref.listen(
fireImmediately: true,
generalSettingsWithDefaultsProvider.select(
(settings) => settings.useExternalDownloadManager,
),
(previous, next) async {
await _service.setUseExternalDownloadManager(next);
},
onError: (error, stackTrace) {
logger.e(
'Error listening to useExternalDownloadManager',
error: error,
stackTrace: stackTrace,
);
},
);
ref.listen(
fireImmediately: true,
engineSettingsRepositoryProvider,
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
}
String _$engineSettingsReplicationServiceHash() =>
r'3e06f0c273090f9f9dfc42f2140d60ef9557f176';
r'7a4db374b521e592847a85cb8d2aa184be788ee4';
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
void build();
@@ -49,6 +49,7 @@ class TabsBehaviorSettingsScreen extends StatelessWidget {
_TabCreationSection(),
_TabOrganizationSection(),
_TabInteractionSection(),
_DownloadsSection(),
],
);
},
@@ -404,3 +405,45 @@ class _AppLinksModeSection extends HookConsumerWidget {
);
}
}
class _DownloadsSection extends StatelessWidget {
const _DownloadsSection();
@override
Widget build(BuildContext context) {
return const Column(
children: [
SettingSection(name: 'Downloads'),
_ExternalDownloadManagerTile(),
],
);
}
}
class _ExternalDownloadManagerTile extends HookConsumerWidget {
const _ExternalDownloadManagerTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final useExternalDownloadManager = ref.watch(
generalSettingsWithDefaultsProvider.select(
(s) => s.useExternalDownloadManager,
),
);
return SwitchListTile.adaptive(
title: const Text('Use external download manager'),
subtitle: const Text('Manage downloads with another app'),
secondary: const Icon(MdiIcons.download),
value: useExternalDownloadManager,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.useExternalDownloadManager(value),
);
},
);
}
}
@@ -81,6 +81,7 @@ class GeneralSettings with FastEquatable {
final TabBarPosition tabBarPosition;
final QuickTabSwitcherMode quickTabSwitcherMode;
final bool pullToRefreshEnabled;
final bool useExternalDownloadManager;
final bool doubleBackCloseTab;
final Duration unassignedTabsAutoCleanInterval;
final int maxSearchHistoryEntries;
@@ -107,6 +108,7 @@ class GeneralSettings with FastEquatable {
required this.tabBarPosition,
required this.quickTabSwitcherMode,
required this.pullToRefreshEnabled,
required this.useExternalDownloadManager,
required this.doubleBackCloseTab,
required this.unassignedTabsAutoCleanInterval,
required this.maxSearchHistoryEntries,
@@ -134,6 +136,7 @@ class GeneralSettings with FastEquatable {
TabBarPosition? tabBarPosition,
QuickTabSwitcherMode? quickTabSwitcherMode,
bool? pullToRefreshEnabled,
bool? useExternalDownloadManager,
bool? doubleBackCloseTab,
Duration? unassignedTabsAutoCleanInterval,
int? maxSearchHistoryEntries,
@@ -161,6 +164,7 @@ class GeneralSettings with FastEquatable {
quickTabSwitcherMode =
quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs,
pullToRefreshEnabled = pullToRefreshEnabled ?? true,
useExternalDownloadManager = useExternalDownloadManager ?? false,
doubleBackCloseTab = doubleBackCloseTab ?? true,
unassignedTabsAutoCleanInterval =
unassignedTabsAutoCleanInterval ?? Duration.zero,
@@ -194,6 +198,7 @@ class GeneralSettings with FastEquatable {
tabBarPosition,
quickTabSwitcherMode,
pullToRefreshEnabled,
useExternalDownloadManager,
doubleBackCloseTab,
unassignedTabsAutoCleanInterval,
maxSearchHistoryEntries,
@@ -59,6 +59,8 @@ abstract class _$GeneralSettingsCWProxy {
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled);
GeneralSettings useExternalDownloadManager(bool useExternalDownloadManager);
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab);
GeneralSettings unassignedTabsAutoCleanInterval(
@@ -96,6 +98,7 @@ abstract class _$GeneralSettingsCWProxy {
TabBarPosition tabBarPosition,
QuickTabSwitcherMode quickTabSwitcherMode,
bool pullToRefreshEnabled,
bool useExternalDownloadManager,
bool doubleBackCloseTab,
Duration unassignedTabsAutoCleanInterval,
int maxSearchHistoryEntries,
@@ -197,6 +200,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) =>
call(pullToRefreshEnabled: pullToRefreshEnabled);
@override
GeneralSettings useExternalDownloadManager(bool useExternalDownloadManager) =>
call(useExternalDownloadManager: useExternalDownloadManager);
@override
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab) =>
call(doubleBackCloseTab: doubleBackCloseTab);
@@ -240,6 +247,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? tabBarPosition = const $CopyWithPlaceholder(),
Object? quickTabSwitcherMode = const $CopyWithPlaceholder(),
Object? pullToRefreshEnabled = const $CopyWithPlaceholder(),
Object? useExternalDownloadManager = const $CopyWithPlaceholder(),
Object? doubleBackCloseTab = const $CopyWithPlaceholder(),
Object? unassignedTabsAutoCleanInterval = const $CopyWithPlaceholder(),
Object? maxSearchHistoryEntries = const $CopyWithPlaceholder(),
@@ -367,6 +375,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.pullToRefreshEnabled
// ignore: cast_nullable_to_non_nullable
: pullToRefreshEnabled as bool,
useExternalDownloadManager:
useExternalDownloadManager == const $CopyWithPlaceholder() ||
useExternalDownloadManager == null
? _value.useExternalDownloadManager
// ignore: cast_nullable_to_non_nullable
: useExternalDownloadManager as bool,
doubleBackCloseTab:
doubleBackCloseTab == const $CopyWithPlaceholder() ||
doubleBackCloseTab == null
@@ -450,6 +464,7 @@ GeneralSettings _$GeneralSettingsFromJson(
json['quickTabSwitcherMode'],
),
pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?,
useExternalDownloadManager: json['useExternalDownloadManager'] as bool?,
doubleBackCloseTab: json['doubleBackCloseTab'] as bool?,
unassignedTabsAutoCleanInterval:
json['unassignedTabsAutoCleanInterval'] == null
@@ -493,6 +508,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'quickTabSwitcherMode':
_$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!,
'pullToRefreshEnabled': instance.pullToRefreshEnabled,
'useExternalDownloadManager': instance.useExternalDownloadManager,
'doubleBackCloseTab': instance.doubleBackCloseTab,
'unassignedTabsAutoCleanInterval':
instance.unassignedTabsAutoCleanInterval.inMicroseconds,
@@ -126,6 +126,8 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.bool,
db.typeMapping,
),
'useExternalDownloadManager': settings['useExternalDownloadManager']
?.readAs(DriftSqlType.bool, db.typeMapping),
'doubleBackCloseTab': settings['doubleBackCloseTab']?.readAs(
DriftSqlType.bool,
db.typeMapping,
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
}
String _$generalSettingsRepositoryHash() =>
r'0d4b01ff15942b513a7cdceee7bf43721ae1c074';
r'7cea68308e2e7847e9bb030e9c9c04860c760edd';
abstract class _$GeneralSettingsRepository
extends $StreamNotifier<GeneralSettings> {
@@ -0,0 +1,379 @@
# External Download Manager Feature - Implementation Plan
## Overview
Implement support for external download managers (ADM, 1DM, AB DM) as requested in GitHub issue #146.
The feature uses a database-stored setting (similar to AppLinks) rather than SharedPreferences,
and configures Mozilla's DownloadsFeature to forward downloads to third-party apps.
## Reference Implementation (Fenix)
- **Preference Key**: `pref_key_external_download_manager`
- **Usage in Fenix**:
- `fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt:759` - DownloadsFeature configuration
- `fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt:93` - DownloadsFeature in addons
- `fenix/app/src/main/res/xml/downloads_settings_preferences.xml:8` - Settings UI XML
- `fenix/app/src/main/res/values/preference_keys.xml:503` - Preference key definition
## WebLibre Pattern Reference (AppLinks)
The implementation should follow the existing AppLinks pattern as a reference:
- **Model**: `app/lib/features/user/data/models/general_settings.dart` - AppLinksMode not here (it's in native), but this shows the pattern
- **Repository**: `app/lib/features/user/domain/repositories/general_settings.dart:37-195` - GeneralSettingsRepository pattern
- **Native Storage**: `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt:325-352` - AppLinks storage in SharedPreferences (our implementation will use global state set by Flutter)
- **Native Usage**: `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt:60-74` - shouldOpenLinksInApp/shouldPromptOpenLinksInApp pattern
- **Pigeon API**: `packages/flutter_mozilla_components/pigeons/gecko.dart:643-653` - AppLinksMode enum
- **Provider**: `app/lib/features/geckoview/features/browser/domain/providers.dart:382-395` - AppLinksModeNotifier
- **Service**: `packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart:142-150` - setAppLinksMode/getAppLinksMode
## Architecture
### Data Flow
1. **Database Storage**: Setting stored in `user.db` -> `setting` table with partition_key='general'
2. **Flutter Layer**: GeneralSettings model manages the setting state
3. **Pigeon Bridge**: `setUseExternalDownloadManager(bool)`/`getUseExternalDownloadManager()` sync to native
4. **Native Layer**: GlobalComponents stores value as `var useExternalDownloadManager: Boolean`
5. **DownloadsFeature**: Uses `shouldForwardToThirdParties = { GlobalComponents.useExternalDownloadManager }`
### Key Components
- **Setting Type**: Boolean (default: false)
- **Storage Location**: user.db, setting table, partition_key='general', key='useExternalDownloadManager'
- **Native Integration**: Mozilla Android Components DownloadsFeature's shouldForwardToThirdParties callback
## Implementation Steps
### Phase 1: Data Model & Database Layer
#### Step 1.1 - Update GeneralSettings Model
**File**: `app/lib/features/user/data/models/general_settings.dart`
**Changes Required**:
- Add new field declaration: `final bool useExternalDownloadManager;`
- Add field to constructor parameters
- Add field to `withDefaults()` constructor (default value: `false`)
- Add field to `fromJson()`/`toJson()` methods via @JsonSerializable annotation
- Add field to `hashParameters` getter for FastEquatable
**Reference**: Look at `pullToRefreshEnabled` field (line 83) as a template
#### Step 1.2 - Update GeneralSettingsRepository
**File**: `app/lib/features/user/domain/repositories/general_settings.dart`
**Changes Required**:
- Add deserialization logic in `_deserializeSettings()` method (around line 142)
- Pattern:
```dart
'useExternalDownloadManager': settings['useExternalDownloadManager']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
```
- This follows the same pattern as existing fields like `pullToRefreshEnabled` (line 125-128)
**Reference**: `_deserializeSettings()` method (lines 39-143) shows how all settings are deserialized
### Phase 2: Pigeon API Definition
#### Step 2.1 - Add Pigeon API Methods
**File**: `packages/flutter_mozilla_components/pigeons/gecko.dart`
**Location**: Find `GeckoEngineSettingsApi` interface (around line 1004-1015)
**Changes Required**:
- Add method declaration before closing brace:
```dart
void setUseExternalDownloadManager(bool enabled);
bool getUseExternalDownloadManager();
```
**Reference**: Similar to `setAppLinksMode` and `getAppLinksMode` methods (lines 1012-1014)
**Important**: After modifying this file, must run `melos build-pigeons --no-select` to regenerate interfaces
### Phase 3: Flutter Service Layer
#### Step 3.1 - Update GeckoEngineSettingsService
**File**: `packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart`
**Location**: After `getAppLinksMode()` method (around line 148-151)
**Changes Required**:
- Add two methods to wrap Pigeon API calls:
```dart
Future<void> setUseExternalDownloadManager(bool enabled) {
return _api.setUseExternalDownloadManager(enabled);
}
Future<bool> getUseExternalDownloadManager() {
return _api.getUseExternalDownloadManager();
}
```
**Reference**: Follow pattern of `setAppLinksMode()`/`getAppLinksMode()` methods (lines 144-150)
#### Step 3.2 - Add Riverpod Provider
**File**: `app/lib/features/geckoview/features/browser/domain/providers.dart`
**Location**: After `AppLinksModeNotifier` class (around line 395)
**Changes Required**:
- Add new provider class:
```dart
@Riverpod(keepAlive: true)
class ExternalDownloadManagerNotifier extends _$ExternalDownloadManagerNotifier {
final _service = GeckoEngineSettingsService();
Future<void> setEnabled(bool enabled) async {
await _service.setUseExternalDownloadManager(enabled);
ref.invalidateSelf();
}
@override
Future<bool> build() {
return _service.getUseExternalDownloadManager();
}
}
```
**Reference**: Follow `AppLinksModeNotifier` pattern (lines 382-395)
**Important**: After modifying this file, must run `melos build --no-select` to generate provider code
### Phase 4: Native Implementation (Kotlin)
#### Step 4.1 - Implement Pigeon API Methods
**File**: `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt`
**Location**: After `getAppLinksMode()` method (around line 353)
**Changes Required**:
- Add method implementations:
```kotlin
override fun setUseExternalDownloadManager(enabled: Boolean) {
GlobalComponents.useExternalDownloadManager = enabled
}
override fun getUseExternalDownloadManager(): Boolean {
return GlobalComponents.useExternalDownloadManager ?: false
}
```
**Reference**: Follow `setAppLinksMode()`/`getAppLinksMode()` implementation pattern (lines 325-352)
**Note**: Unlike AppLinks which uses SharedPreferences, we use a global variable that Flutter controls
#### Step 4.2 - Add Global State Variable
**File**: `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt`
**Location**: After other engine settings state (around line 74)
**Changes Required**:
- Add companion object variable:
```kotlin
var useExternalDownloadManager: Boolean = false
```
**Reference**: Similar to `engineSettingsApi` and `pullToRefreshEnabled` variables (lines 58, 61)
#### Step 4.3 - Configure DownloadsFeature
**File**: `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/BaseBrowserFragment.kt`
**Location**: Find `downloadsFeature.set(...)` call (around line 271-293)
**Changes Required**:
- Add `shouldForwardToThirdParties` parameter to `DownloadsFeature` constructor:
```kotlin
downloadsFeature.set(
feature = DownloadsFeature(
// ... existing parameters ...
onNeedToRequestPermissions = { permissions ->
requestDownloadPermissionsLauncher.launch(permissions)
},
shouldForwardToThirdParties = {
GlobalComponents.useExternalDownloadManager
},
),
owner = this,
view = view,
)
```
**Reference**: Fenix implementation at `fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt:757-762`
### Phase 5: UI Implementation
#### Step 5.1 - Create Downloads Settings Section
**File**: `app/lib/features/settings/presentation/screens/privacy_security_settings.dart`
**Location**: In the `build()` method's ListView children list (around line 50)
**Changes Required**:
- Add `_DownloadsSection()` to children array after `_DataManagementSection()`
- Create new widget class:
```dart
class _DownloadsSection extends StatelessWidget {
const _DownloadsSection();
@override
Widget build(BuildContext context) {
return const Column(
children: [
SettingSection(name: 'Downloads'),
_ExternalDownloadManagerTile(),
],
);
}
}
```
**Reference**: Look at `_PrivacyModesSection` (lines 65-78) or `_TrackingProtectionSection` patterns
#### Step 5.2 - Create Toggle Tile Widget
**File**: `app/lib/features/settings/presentation/screens/privacy_security_settings.dart`
**Location**: Add after the new `_DownloadsSection` class
**Changes Required**:
- Create `HookConsumerWidget` for the toggle:
```dart
class _ExternalDownloadManagerTile extends HookConsumerWidget {
const _ExternalDownloadManagerTile();
@override
Widget build(BuildContext context) {
final useExternalDownloadManager = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.useExternalDownloadManager),
);
return SettingToggleTile(
title: Text('Use external download manager'),
subtitle: Text('Forward downloads to apps like ADM, 1DM, AB DM'),
value: useExternalDownloadManager,
onChanged: (value) async {
await ref
.read(generalSettingsRepositoryProvider.notifier)
.updateSettings((s) => s.copyWith(useExternalDownloadManager: value));
},
);
}
}
```
**Reference**: Look at `_IncognitoModeSection` or other toggle implementations in the same file
**Pattern Reference**: `pullToRefreshEnabled` toggle at `app/lib/features/settings/presentation/screens/appearance_display_settings.dart:385-408`
### Phase 6: Synchronization & Initialization
#### Step 6.1 - Initial Synchronization
**Consideration**: When the app starts, we need to ensure the native side has the correct value.
**Options**:
1. **Option A**: Add to initialization code in `app/lib/domain/services/app_initialization.dart`
2. **Option B**: Let the provider's `build()` method handle it naturally (recommended)
3. **Option C**: Add to `app/lib/features/geckoview/features/browser/domain/providers.dart` initialization
**Recommended Approach**: Let the Riverpod provider handle it. When UI first renders, it will:
- Read from database via `generalSettingsRepositoryProvider`
- Provider's `build()` calls `getUseExternalDownloadManager()` from native
- The provider pattern will handle setting the value on native side when changed
**Note**: Unlike AppLinks (which reads from SharedPreferences in native code), this pattern relies on Flutter being the source of truth and syncing to native.
## Build Commands
After completing implementation changes, run these commands in order:
```bash
# 1. Regenerate Pigeon interfaces (after modifying gecko.dart)
melos build-pigeons --no-select
# 2. Regenerate Riverpod providers and models (after modifying providers.dart or models)
melos build --no-select
```
## Testing Checklist
- [ ] Setting toggle appears in Privacy & Security settings
- [ ] Toggle state persists across app restarts
- [ ] When enabled, downloads are forwarded to external apps
- [ ] When disabled, downloads use built-in manager
- [ ] Setting value is correctly stored in user.db
- [ ] Native side correctly reads value from Flutter
- [ ] DownloadsFeature uses correct shouldForwardToThirdParties value
- [ ] No SharedPreferences usage (requirement verified)
## File Modification Summary
| # | File | Phase | Change Type |
|---|------|-------|-------------|
| 1 | `app/lib/features/user/data/models/general_settings.dart` | 1.1 | Add model field |
| 2 | `app/lib/features/user/domain/repositories/general_settings.dart` | 1.2 | Add deserialization |
| 3 | `packages/flutter_mozilla_components/pigeons/gecko.dart` | 2.1 | Add Pigeon API methods |
| 4 | `packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart` | 3.1 | Add service wrapper methods |
| 5 | `app/lib/features/geckoview/features/browser/domain/providers.dart` | 3.2 | Add Riverpod provider |
| 6 | `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt` | 4.1 | Implement native methods |
| 7 | `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt` | 4.2 | Add global state variable |
| 8 | `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/BaseBrowserFragment.kt` | 4.3 | Configure DownloadsFeature |
| 9 | `app/lib/features/settings/presentation/screens/privacy_security_settings.dart` | 5.1, 5.2 | Add UI widgets |
## Important Notes
### Design Decisions
1. **No SharedPreferences in Native Code**: Unlike AppLinks which uses SharedPreferences, this feature uses a global variable set by Flutter. This ensures Flutter is the single source of truth.
2. **Database as Storage**: Setting is stored in the database (user.db, setting table) following the same pattern as other GeneralSettings.
3. **Provider Architecture**: Uses `@Riverpod(keepAlive: true)` to ensure the provider persists across widget rebuilds.
4. **Async Provider**: The provider returns `Future<bool>` because native Pigeon calls are asynchronous.
5. **Auto-Dispose Prevention**: `keepAlive: true` ensures the provider isn't disposed when no widgets are listening, which is important for maintaining sync state.
### Mozilla Android Components Integration
The `shouldForwardToThirdParties` callback in `DownloadsFeature` is invoked for each download request:
- When `true`: Download is forwarded to an external app via intent
- When `false`: Download is handled internally by FetchDownloadManager
This is the exact same mechanism used in Fenix for the same feature.
### Code Generation
Both `melos build-pigeons --no-select` and `melos build --no-select` are required because:
- Pigeon generates platform channel interfaces
- Riverpod generates provider code
- JSON serialization generates model methods
- CopyWith extension generates copyWith methods
Run these commands after modifying any annotated files.
## Related Code References
### Database Schema
**File**: `app/lib/features/user/data/database/definitions.drift:1-5`
- Setting table structure
- Uses Drift's `ANY` type for flexible value storage
### DAO Pattern
**File**: `app/lib/features/user/data/database/daos/setting.dart:29-62`
- `updateSetting()` method
- `getAllSettingsOfPartitionKey()` method
- Pattern for partition-based settings
### Settings UI Pattern
**File**: `app/lib/features/settings/presentation/screens/appearance_display_settings.dart:385-408`
- Example of toggle tile implementation
- Shows how to watch and update settings
### Native Feature Integration
**File**: `packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt:60-74`
- Pattern for shouldOpenLinksInApp and shouldPromptOpenLinksInApp
- Similar to how shouldForwardToThirdParties will be used
## Success Criteria
Feature is complete when:
1. User can toggle "Use external download manager" in Privacy & Security settings
2. Toggle state persists in database (user.db, setting table)
3. When enabled, clicking download links prompts to open in external apps
4. When disabled, downloads use built-in download manager
5. Setting syncs correctly between Flutter and native layers
6. No SharedPreferences are used for this feature (verified requirement)
7. Code follows existing WebLibre patterns (GeneralSettings, AppLinks, etc.)
@@ -287,6 +287,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
onNeedToRequestPermissions = { permissions ->
requestDownloadPermissionsLauncher.launch(permissions)
},
shouldForwardToThirdParties = {
GlobalComponents.useExternalDownloadManager
},
),
owner = this,
view = view,
@@ -57,6 +57,9 @@ object GlobalComponents {
// Engine settings API for managing engine-specific settings
var engineSettingsApi: GeckoEngineSettingsApiImpl? = null
// External download manager setting
var useExternalDownloadManager: Boolean = false
fun shouldOpenLinksInApp(): Boolean {
return when (engineSettingsApi!!.getAppLinksMode()) {
eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ALWAYS -> true
@@ -350,4 +350,12 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi {
else -> AppLinksMode.ASK
}
}
override fun setUseExternalDownloadManager(enabled: Boolean) {
GlobalComponents.useExternalDownloadManager = enabled
}
override fun getUseExternalDownloadManager(): Boolean {
return GlobalComponents.useExternalDownloadManager
}
}
@@ -3850,6 +3850,12 @@ interface GeckoEngineSettingsApi {
*/
fun setAppLinksMode(mode: AppLinksMode)
fun getAppLinksMode(): AppLinksMode
/**
* Sets whether to use external download managers for downloads.
* When enabled, downloads are forwarded to third-party apps like ADM, 1DM, AB DM.
*/
fun setUseExternalDownloadManager(enabled: Boolean)
fun getUseExternalDownloadManager(): Boolean
companion object {
/** The codec used by GeckoEngineSettingsApi. */
@@ -3947,6 +3953,39 @@ interface GeckoEngineSettingsApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setUseExternalDownloadManager$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val enabledArg = args[0] as Boolean
val wrapped: List<Any?> = try {
api.setUseExternalDownloadManager(enabledArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.getUseExternalDownloadManager$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getUseExternalDownloadManager())
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -148,4 +148,14 @@ class GeckoEngineSettingsService {
Future<AppLinksMode> getAppLinksMode() {
return _api.getAppLinksMode();
}
/// Sets whether to use external download managers for downloads.
/// When enabled, downloads are forwarded to third-party apps like ADM, 1DM, AB DM.
Future<void> setUseExternalDownloadManager(bool enabled) {
return _api.setUseExternalDownloadManager(enabled);
}
Future<bool> getUseExternalDownloadManager() {
return _api.getUseExternalDownloadManager();
}
}
@@ -4481,6 +4481,57 @@ class GeckoEngineSettingsApi {
return (pigeonVar_replyList[0] as AppLinksMode?)!;
}
}
/// Sets whether to use external download managers for downloads.
/// When enabled, downloads are forwarded to third-party apps like ADM, 1DM, AB DM.
Future<void> setUseExternalDownloadManager(bool enabled) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setUseExternalDownloadManager$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[enabled]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
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<bool> getUseExternalDownloadManager() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.getUseExternalDownloadManager$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
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 bool?)!;
}
}
}
class GeckoSessionApi {
@@ -1012,6 +1012,12 @@ abstract class GeckoEngineSettingsApi {
void setAppLinksMode(AppLinksMode mode);
AppLinksMode getAppLinksMode();
/// Sets whether to use external download managers for downloads.
/// When enabled, downloads are forwarded to third-party apps like ADM, 1DM, AB DM.
void setUseExternalDownloadManager(bool enabled);
bool getUseExternalDownloadManager();
}
@HostApi()