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 50ce78cf..3e4c168f 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 @@ -61,6 +61,23 @@ class EngineSettingsReplicationService }, ); + ref.listen( + fireImmediately: true, + generalSettingsWithDefaultsProvider.select( + (settings) => settings.pullToRefreshEnabled, + ), + (previous, next) async { + await _service.setPullToRefreshEnabled(next); + }, + onError: (error, stackTrace) { + logger.e( + 'Error listening to pullToRefreshEnabled', + error: error, + stackTrace: stackTrace, + ); + }, + ); + ref.listen( fireImmediately: true, engineSettingsRepositoryProvider, 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 60446a8d..e70b0520 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'7583ebc8ca9c11377486ff5a3e763493dcdc903a'; + r'0a9539e19946028f525de64ed5f33e2483e7abc2'; abstract class _$EngineSettingsReplicationService extends $Notifier { void build(); diff --git a/app/lib/features/settings/presentation/screens/general_settings.dart b/app/lib/features/settings/presentation/screens/general_settings.dart index 29319471..c2f70b41 100644 --- a/app/lib/features/settings/presentation/screens/general_settings.dart +++ b/app/lib/features/settings/presentation/screens/general_settings.dart @@ -66,6 +66,7 @@ class GeneralSettingsScreen extends StatelessWidget { _AutoHideTabBarTile(), _BottomSheetTabViewTile(), _TabBarSwipeBehaviorSection(), + _PullToRefreshTile(), _IconCacheTile(), ], ); @@ -840,6 +841,32 @@ class _TabBarSwipeBehaviorSection extends HookConsumerWidget { } } +class _PullToRefreshTile extends HookConsumerWidget { + const _PullToRefreshTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pullToRefreshEnabled = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.pullToRefreshEnabled), + ); + + return SwitchListTile.adaptive( + title: const Text('Pull to Refresh'), + subtitle: const Text('Swipe down on pages to reload them'), + secondary: const Icon(MdiIcons.gestureSwipeDown), + value: pullToRefreshEnabled, + onChanged: (value) async { + await ref + .read(saveGeneralSettingsControllerProvider.notifier) + .save( + (currentSettings) => + currentSettings.copyWith.pullToRefreshEnabled(value), + ); + }, + ); + } +} + class _IconCacheTile extends HookConsumerWidget { const _IconCacheTile(); diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index 45232865..f677c1e6 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -80,6 +80,7 @@ class GeneralSettings with FastEquatable { final bool tabBarShowQuickTabSwitcherBar; final TabBarPosition tabBarPosition; final QuickTabSwitcherMode quickTabSwitcherMode; + final bool pullToRefreshEnabled; GeneralSettings({ required this.themeMode, @@ -102,6 +103,7 @@ class GeneralSettings with FastEquatable { required this.tabBarShowQuickTabSwitcherBar, required this.tabBarPosition, required this.quickTabSwitcherMode, + required this.pullToRefreshEnabled, }); GeneralSettings.withDefaults({ @@ -125,6 +127,7 @@ class GeneralSettings with FastEquatable { bool? tabBarShowQuickTabSwitcherBar, TabBarPosition? tabBarPosition, QuickTabSwitcherMode? quickTabSwitcherMode, + bool? pullToRefreshEnabled, }) : themeMode = themeMode ?? ThemeMode.dark, enableReadability = enableReadability ?? true, enforceReadability = enforceReadability ?? false, @@ -147,7 +150,8 @@ class GeneralSettings with FastEquatable { tabBarShowQuickTabSwitcherBar = tabBarShowQuickTabSwitcherBar ?? true, tabBarPosition = tabBarPosition ?? TabBarPosition.bottom, quickTabSwitcherMode = - quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs; + quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs, + pullToRefreshEnabled = pullToRefreshEnabled ?? true; factory GeneralSettings.fromJson(Map json) => _$GeneralSettingsFromJson(json); @@ -176,5 +180,6 @@ class GeneralSettings with FastEquatable { tabBarShowQuickTabSwitcherBar, tabBarPosition, quickTabSwitcherMode, + pullToRefreshEnabled, ]; } diff --git a/app/lib/features/user/data/models/general_settings.g.dart b/app/lib/features/user/data/models/general_settings.g.dart index b167e405..a5df805a 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -57,6 +57,8 @@ abstract class _$GeneralSettingsCWProxy { QuickTabSwitcherMode quickTabSwitcherMode, ); + GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled); + /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. /// @@ -85,6 +87,7 @@ abstract class _$GeneralSettingsCWProxy { bool tabBarShowQuickTabSwitcherBar, TabBarPosition tabBarPosition, QuickTabSwitcherMode quickTabSwitcherMode, + bool pullToRefreshEnabled, }); } @@ -179,6 +182,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { QuickTabSwitcherMode quickTabSwitcherMode, ) => call(quickTabSwitcherMode: quickTabSwitcherMode); + @override + GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) => + call(pullToRefreshEnabled: pullToRefreshEnabled); + @override /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. @@ -208,6 +215,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? tabBarShowQuickTabSwitcherBar = const $CopyWithPlaceholder(), Object? tabBarPosition = const $CopyWithPlaceholder(), Object? quickTabSwitcherMode = const $CopyWithPlaceholder(), + Object? pullToRefreshEnabled = const $CopyWithPlaceholder(), }) { return GeneralSettings( themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null @@ -326,6 +334,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.quickTabSwitcherMode // ignore: cast_nullable_to_non_nullable : quickTabSwitcherMode as QuickTabSwitcherMode, + pullToRefreshEnabled: + pullToRefreshEnabled == const $CopyWithPlaceholder() || + pullToRefreshEnabled == null + ? _value.pullToRefreshEnabled + // ignore: cast_nullable_to_non_nullable + : pullToRefreshEnabled as bool, ); } } @@ -390,6 +404,7 @@ GeneralSettings _$GeneralSettingsFromJson( _$QuickTabSwitcherModeEnumMap, json['quickTabSwitcherMode'], ), + pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?, ); Map _$GeneralSettingsToJson( @@ -423,6 +438,7 @@ Map _$GeneralSettingsToJson( 'tabBarPosition': _$TabBarPositionEnumMap[instance.tabBarPosition]!, 'quickTabSwitcherMode': _$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!, + 'pullToRefreshEnabled': instance.pullToRefreshEnabled, }; const _$ThemeModeEnumMap = { diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index 5f492b46..a6fc8a85 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -122,6 +122,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.string, db.typeMapping, ), + 'pullToRefreshEnabled': settings['pullToRefreshEnabled']?.readAs( + DriftSqlType.bool, + db.typeMapping, + ), }); } diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index 3a59bbab..42787471 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider } String _$generalSettingsRepositoryHash() => - r'74db28f2adc4e35a4c00bb2b7f137ed05a3bc662'; + r'dceccac3a93e96907c7bd2b4c58f5021912553ac'; abstract class _$GeneralSettingsRepository extends $StreamNotifier { 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 12333b41..61df102b 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 @@ -242,6 +242,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit view = view, ) + // Apply pull-to-refresh setting + binding.swipeToRefresh.isEnabled = GlobalComponents.pullToRefreshEnabled + GlobalComponents.onPullToRefreshEnabledChanged = { enabled -> + _binding?.swipeToRefresh?.isEnabled = enabled + } + shareResourceFeature.set( ShareResourceFeature( context = components.profileApplicationContext, @@ -549,6 +555,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit override fun onDestroyView() { super.onDestroyView() + GlobalComponents.onPullToRefreshEnabledChanged = null components.engineView?.setActivityContext(null) _binding = null } 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 2abb7402..a7616d4d 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 @@ -36,6 +36,15 @@ object GlobalComponents { val components: Components? get() = _components + // Pull-to-refresh setting + var pullToRefreshEnabled: Boolean = true + set(value) { + field = value + onPullToRefreshEnabledChanged?.invoke(value) + } + + var onPullToRefreshEnabledChanged: ((Boolean) -> Unit)? = null + @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/GeckoEngineSettingsApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt index 7fbf6f67..7463b037 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,6 @@ package eu.weblibre.flutter_mozilla_components.api -import eu.weblibre.flutter_mozilla_components.EngineProvider import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.pigeons.ColorScheme import eu.weblibre.flutter_mozilla_components.pigeons.CookieBannerHandlingMode @@ -243,4 +242,8 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi { components.useCases.sessionUseCases.reload() } } + + override fun setPullToRefreshEnabled(enabled: Boolean) { + GlobalComponents.pullToRefreshEnabled = enabled + } } 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 f02436a5..c572eb91 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 @@ -3504,6 +3504,7 @@ interface GeckoBrowserApi { interface GeckoEngineSettingsApi { fun setDefaultSettings(settings: GeckoEngineSettings) fun updateRuntimeSettings(settings: GeckoEngineSettings) + fun setPullToRefreshEnabled(enabled: Boolean) companion object { /** The codec used by GeckoEngineSettingsApi. */ @@ -3550,6 +3551,24 @@ interface GeckoEngineSettingsApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setPullToRefreshEnabled$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val enabledArg = args[0] as Boolean + val wrapped: List = try { + api.setPullToRefreshEnabled(enabledArg) + listOf(null) + } catch (exception: Throwable) { + GeckoPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } 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 4767c0a1..acb7cd1f 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 @@ -100,4 +100,8 @@ class GeckoEngineSettingsService { GeckoEngineSettings(fingerprintingProtectionOverrides: state), ); } + + Future setPullToRefreshEnabled(bool enabled) { + return _api.setPullToRefreshEnabled(enabled); + } } 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 ecec1f18..3dd56869 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -4077,6 +4077,28 @@ class GeckoEngineSettingsApi { return; } } + + Future setPullToRefreshEnabled(bool enabled) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setPullToRefreshEnabled$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([enabled]); + 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; + } + } } class GeckoSessionApi { diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index 94bde099..df58a7c4 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -915,6 +915,7 @@ abstract class GeckoBrowserApi { abstract class GeckoEngineSettingsApi { void setDefaultSettings(GeckoEngineSettings settings); void updateRuntimeSettings(GeckoEngineSettings settings); + void setPullToRefreshEnabled(bool enabled); } @HostApi()