support for external download managers
This commit is contained in:
+17
@@ -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(
|
ref.listen(
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
engineSettingsRepositoryProvider,
|
engineSettingsRepositoryProvider,
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$engineSettingsReplicationServiceHash() =>
|
String _$engineSettingsReplicationServiceHash() =>
|
||||||
r'3e06f0c273090f9f9dfc42f2140d60ef9557f176';
|
r'7a4db374b521e592847a85cb8d2aa184be788ee4';
|
||||||
|
|
||||||
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
|
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class TabsBehaviorSettingsScreen extends StatelessWidget {
|
|||||||
_TabCreationSection(),
|
_TabCreationSection(),
|
||||||
_TabOrganizationSection(),
|
_TabOrganizationSection(),
|
||||||
_TabInteractionSection(),
|
_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 TabBarPosition tabBarPosition;
|
||||||
final QuickTabSwitcherMode quickTabSwitcherMode;
|
final QuickTabSwitcherMode quickTabSwitcherMode;
|
||||||
final bool pullToRefreshEnabled;
|
final bool pullToRefreshEnabled;
|
||||||
|
final bool useExternalDownloadManager;
|
||||||
final bool doubleBackCloseTab;
|
final bool doubleBackCloseTab;
|
||||||
final Duration unassignedTabsAutoCleanInterval;
|
final Duration unassignedTabsAutoCleanInterval;
|
||||||
final int maxSearchHistoryEntries;
|
final int maxSearchHistoryEntries;
|
||||||
@@ -107,6 +108,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
required this.tabBarPosition,
|
required this.tabBarPosition,
|
||||||
required this.quickTabSwitcherMode,
|
required this.quickTabSwitcherMode,
|
||||||
required this.pullToRefreshEnabled,
|
required this.pullToRefreshEnabled,
|
||||||
|
required this.useExternalDownloadManager,
|
||||||
required this.doubleBackCloseTab,
|
required this.doubleBackCloseTab,
|
||||||
required this.unassignedTabsAutoCleanInterval,
|
required this.unassignedTabsAutoCleanInterval,
|
||||||
required this.maxSearchHistoryEntries,
|
required this.maxSearchHistoryEntries,
|
||||||
@@ -134,6 +136,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
TabBarPosition? tabBarPosition,
|
TabBarPosition? tabBarPosition,
|
||||||
QuickTabSwitcherMode? quickTabSwitcherMode,
|
QuickTabSwitcherMode? quickTabSwitcherMode,
|
||||||
bool? pullToRefreshEnabled,
|
bool? pullToRefreshEnabled,
|
||||||
|
bool? useExternalDownloadManager,
|
||||||
bool? doubleBackCloseTab,
|
bool? doubleBackCloseTab,
|
||||||
Duration? unassignedTabsAutoCleanInterval,
|
Duration? unassignedTabsAutoCleanInterval,
|
||||||
int? maxSearchHistoryEntries,
|
int? maxSearchHistoryEntries,
|
||||||
@@ -161,6 +164,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
quickTabSwitcherMode =
|
quickTabSwitcherMode =
|
||||||
quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs,
|
quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs,
|
||||||
pullToRefreshEnabled = pullToRefreshEnabled ?? true,
|
pullToRefreshEnabled = pullToRefreshEnabled ?? true,
|
||||||
|
useExternalDownloadManager = useExternalDownloadManager ?? false,
|
||||||
doubleBackCloseTab = doubleBackCloseTab ?? true,
|
doubleBackCloseTab = doubleBackCloseTab ?? true,
|
||||||
unassignedTabsAutoCleanInterval =
|
unassignedTabsAutoCleanInterval =
|
||||||
unassignedTabsAutoCleanInterval ?? Duration.zero,
|
unassignedTabsAutoCleanInterval ?? Duration.zero,
|
||||||
@@ -194,6 +198,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
tabBarPosition,
|
tabBarPosition,
|
||||||
quickTabSwitcherMode,
|
quickTabSwitcherMode,
|
||||||
pullToRefreshEnabled,
|
pullToRefreshEnabled,
|
||||||
|
useExternalDownloadManager,
|
||||||
doubleBackCloseTab,
|
doubleBackCloseTab,
|
||||||
unassignedTabsAutoCleanInterval,
|
unassignedTabsAutoCleanInterval,
|
||||||
maxSearchHistoryEntries,
|
maxSearchHistoryEntries,
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
|
|
||||||
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled);
|
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled);
|
||||||
|
|
||||||
|
GeneralSettings useExternalDownloadManager(bool useExternalDownloadManager);
|
||||||
|
|
||||||
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab);
|
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab);
|
||||||
|
|
||||||
GeneralSettings unassignedTabsAutoCleanInterval(
|
GeneralSettings unassignedTabsAutoCleanInterval(
|
||||||
@@ -96,6 +98,7 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
TabBarPosition tabBarPosition,
|
TabBarPosition tabBarPosition,
|
||||||
QuickTabSwitcherMode quickTabSwitcherMode,
|
QuickTabSwitcherMode quickTabSwitcherMode,
|
||||||
bool pullToRefreshEnabled,
|
bool pullToRefreshEnabled,
|
||||||
|
bool useExternalDownloadManager,
|
||||||
bool doubleBackCloseTab,
|
bool doubleBackCloseTab,
|
||||||
Duration unassignedTabsAutoCleanInterval,
|
Duration unassignedTabsAutoCleanInterval,
|
||||||
int maxSearchHistoryEntries,
|
int maxSearchHistoryEntries,
|
||||||
@@ -197,6 +200,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) =>
|
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) =>
|
||||||
call(pullToRefreshEnabled: pullToRefreshEnabled);
|
call(pullToRefreshEnabled: pullToRefreshEnabled);
|
||||||
|
|
||||||
|
@override
|
||||||
|
GeneralSettings useExternalDownloadManager(bool useExternalDownloadManager) =>
|
||||||
|
call(useExternalDownloadManager: useExternalDownloadManager);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab) =>
|
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab) =>
|
||||||
call(doubleBackCloseTab: doubleBackCloseTab);
|
call(doubleBackCloseTab: doubleBackCloseTab);
|
||||||
@@ -240,6 +247,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
Object? tabBarPosition = const $CopyWithPlaceholder(),
|
Object? tabBarPosition = const $CopyWithPlaceholder(),
|
||||||
Object? quickTabSwitcherMode = const $CopyWithPlaceholder(),
|
Object? quickTabSwitcherMode = const $CopyWithPlaceholder(),
|
||||||
Object? pullToRefreshEnabled = const $CopyWithPlaceholder(),
|
Object? pullToRefreshEnabled = const $CopyWithPlaceholder(),
|
||||||
|
Object? useExternalDownloadManager = const $CopyWithPlaceholder(),
|
||||||
Object? doubleBackCloseTab = const $CopyWithPlaceholder(),
|
Object? doubleBackCloseTab = const $CopyWithPlaceholder(),
|
||||||
Object? unassignedTabsAutoCleanInterval = const $CopyWithPlaceholder(),
|
Object? unassignedTabsAutoCleanInterval = const $CopyWithPlaceholder(),
|
||||||
Object? maxSearchHistoryEntries = const $CopyWithPlaceholder(),
|
Object? maxSearchHistoryEntries = const $CopyWithPlaceholder(),
|
||||||
@@ -367,6 +375,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
? _value.pullToRefreshEnabled
|
? _value.pullToRefreshEnabled
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: pullToRefreshEnabled as bool,
|
: pullToRefreshEnabled as bool,
|
||||||
|
useExternalDownloadManager:
|
||||||
|
useExternalDownloadManager == const $CopyWithPlaceholder() ||
|
||||||
|
useExternalDownloadManager == null
|
||||||
|
? _value.useExternalDownloadManager
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: useExternalDownloadManager as bool,
|
||||||
doubleBackCloseTab:
|
doubleBackCloseTab:
|
||||||
doubleBackCloseTab == const $CopyWithPlaceholder() ||
|
doubleBackCloseTab == const $CopyWithPlaceholder() ||
|
||||||
doubleBackCloseTab == null
|
doubleBackCloseTab == null
|
||||||
@@ -450,6 +464,7 @@ GeneralSettings _$GeneralSettingsFromJson(
|
|||||||
json['quickTabSwitcherMode'],
|
json['quickTabSwitcherMode'],
|
||||||
),
|
),
|
||||||
pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?,
|
pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?,
|
||||||
|
useExternalDownloadManager: json['useExternalDownloadManager'] as bool?,
|
||||||
doubleBackCloseTab: json['doubleBackCloseTab'] as bool?,
|
doubleBackCloseTab: json['doubleBackCloseTab'] as bool?,
|
||||||
unassignedTabsAutoCleanInterval:
|
unassignedTabsAutoCleanInterval:
|
||||||
json['unassignedTabsAutoCleanInterval'] == null
|
json['unassignedTabsAutoCleanInterval'] == null
|
||||||
@@ -493,6 +508,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
|||||||
'quickTabSwitcherMode':
|
'quickTabSwitcherMode':
|
||||||
_$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!,
|
_$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!,
|
||||||
'pullToRefreshEnabled': instance.pullToRefreshEnabled,
|
'pullToRefreshEnabled': instance.pullToRefreshEnabled,
|
||||||
|
'useExternalDownloadManager': instance.useExternalDownloadManager,
|
||||||
'doubleBackCloseTab': instance.doubleBackCloseTab,
|
'doubleBackCloseTab': instance.doubleBackCloseTab,
|
||||||
'unassignedTabsAutoCleanInterval':
|
'unassignedTabsAutoCleanInterval':
|
||||||
instance.unassignedTabsAutoCleanInterval.inMicroseconds,
|
instance.unassignedTabsAutoCleanInterval.inMicroseconds,
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
|||||||
DriftSqlType.bool,
|
DriftSqlType.bool,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
),
|
),
|
||||||
|
'useExternalDownloadManager': settings['useExternalDownloadManager']
|
||||||
|
?.readAs(DriftSqlType.bool, db.typeMapping),
|
||||||
'doubleBackCloseTab': settings['doubleBackCloseTab']?.readAs(
|
'doubleBackCloseTab': settings['doubleBackCloseTab']?.readAs(
|
||||||
DriftSqlType.bool,
|
DriftSqlType.bool,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$generalSettingsRepositoryHash() =>
|
String _$generalSettingsRepositoryHash() =>
|
||||||
r'0d4b01ff15942b513a7cdceee7bf43721ae1c074';
|
r'7cea68308e2e7847e9bb030e9c9c04860c760edd';
|
||||||
|
|
||||||
abstract class _$GeneralSettingsRepository
|
abstract class _$GeneralSettingsRepository
|
||||||
extends $StreamNotifier<GeneralSettings> {
|
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.)
|
||||||
+3
@@ -287,6 +287,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
onNeedToRequestPermissions = { permissions ->
|
onNeedToRequestPermissions = { permissions ->
|
||||||
requestDownloadPermissionsLauncher.launch(permissions)
|
requestDownloadPermissionsLauncher.launch(permissions)
|
||||||
},
|
},
|
||||||
|
shouldForwardToThirdParties = {
|
||||||
|
GlobalComponents.useExternalDownloadManager
|
||||||
|
},
|
||||||
),
|
),
|
||||||
owner = this,
|
owner = this,
|
||||||
view = view,
|
view = view,
|
||||||
|
|||||||
+3
@@ -57,6 +57,9 @@ object GlobalComponents {
|
|||||||
// Engine settings API for managing engine-specific settings
|
// Engine settings API for managing engine-specific settings
|
||||||
var engineSettingsApi: GeckoEngineSettingsApiImpl? = null
|
var engineSettingsApi: GeckoEngineSettingsApiImpl? = null
|
||||||
|
|
||||||
|
// External download manager setting
|
||||||
|
var useExternalDownloadManager: Boolean = false
|
||||||
|
|
||||||
fun shouldOpenLinksInApp(): Boolean {
|
fun shouldOpenLinksInApp(): Boolean {
|
||||||
return when (engineSettingsApi!!.getAppLinksMode()) {
|
return when (engineSettingsApi!!.getAppLinksMode()) {
|
||||||
eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ALWAYS -> true
|
eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode.ALWAYS -> true
|
||||||
|
|||||||
+8
@@ -350,4 +350,12 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi {
|
|||||||
else -> AppLinksMode.ASK
|
else -> AppLinksMode.ASK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setUseExternalDownloadManager(enabled: Boolean) {
|
||||||
|
GlobalComponents.useExternalDownloadManager = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUseExternalDownloadManager(): Boolean {
|
||||||
|
return GlobalComponents.useExternalDownloadManager
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
@@ -3850,6 +3850,12 @@ interface GeckoEngineSettingsApi {
|
|||||||
*/
|
*/
|
||||||
fun setAppLinksMode(mode: AppLinksMode)
|
fun setAppLinksMode(mode: AppLinksMode)
|
||||||
fun getAppLinksMode(): 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 {
|
companion object {
|
||||||
/** The codec used by GeckoEngineSettingsApi. */
|
/** The codec used by GeckoEngineSettingsApi. */
|
||||||
@@ -3947,6 +3953,39 @@ interface GeckoEngineSettingsApi {
|
|||||||
channel.setMessageHandler(null)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -148,4 +148,14 @@ class GeckoEngineSettingsService {
|
|||||||
Future<AppLinksMode> getAppLinksMode() {
|
Future<AppLinksMode> getAppLinksMode() {
|
||||||
return _api.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?)!;
|
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 {
|
class GeckoSessionApi {
|
||||||
|
|||||||
@@ -1012,6 +1012,12 @@ abstract class GeckoEngineSettingsApi {
|
|||||||
void setAppLinksMode(AppLinksMode mode);
|
void setAppLinksMode(AppLinksMode mode);
|
||||||
|
|
||||||
AppLinksMode getAppLinksMode();
|
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()
|
@HostApi()
|
||||||
|
|||||||
Reference in New Issue
Block a user