refactor tab bar behaviour; respect scroll preferences of websites;
This commit is contained in:
@@ -64,6 +64,7 @@ class TabState extends WebPageInfo {
|
|||||||
final bool isPrivate;
|
final bool isPrivate;
|
||||||
final bool isFullScreen;
|
final bool isFullScreen;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
final bool showToolbarAsExpanded;
|
||||||
|
|
||||||
bool get isFinishedLoading => !isLoading && progress == 100;
|
bool get isFinishedLoading => !isLoading && progress == 100;
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ class TabState extends WebPageInfo {
|
|||||||
required this.isPrivate,
|
required this.isPrivate,
|
||||||
required this.isFullScreen,
|
required this.isFullScreen,
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
|
required this.showToolbarAsExpanded,
|
||||||
required this.securityInfoState,
|
required this.securityInfoState,
|
||||||
required this.historyState,
|
required this.historyState,
|
||||||
required this.readerableState,
|
required this.readerableState,
|
||||||
@@ -102,6 +104,7 @@ class TabState extends WebPageInfo {
|
|||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
isFullScreen: false,
|
isFullScreen: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
showToolbarAsExpanded: false,
|
||||||
securityInfoState: SecurityState.$default(),
|
securityInfoState: SecurityState.$default(),
|
||||||
historyState: HistoryState.$default(),
|
historyState: HistoryState.$default(),
|
||||||
readerableState: ReaderableState.$default(),
|
readerableState: ReaderableState.$default(),
|
||||||
@@ -120,6 +123,7 @@ class TabState extends WebPageInfo {
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
isFullScreen,
|
isFullScreen,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
showToolbarAsExpanded,
|
||||||
securityInfoState,
|
securityInfoState,
|
||||||
historyState,
|
historyState,
|
||||||
readerableState,
|
readerableState,
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ abstract class _$TabStateCWProxy {
|
|||||||
|
|
||||||
TabState isLoading(bool isLoading);
|
TabState isLoading(bool isLoading);
|
||||||
|
|
||||||
|
TabState showToolbarAsExpanded(bool showToolbarAsExpanded);
|
||||||
|
|
||||||
TabState securityInfoState(SecurityState securityInfoState);
|
TabState securityInfoState(SecurityState securityInfoState);
|
||||||
|
|
||||||
TabState historyState(HistoryState historyState);
|
TabState historyState(HistoryState historyState);
|
||||||
@@ -53,6 +55,7 @@ abstract class _$TabStateCWProxy {
|
|||||||
bool isPrivate,
|
bool isPrivate,
|
||||||
bool isFullScreen,
|
bool isFullScreen,
|
||||||
bool isLoading,
|
bool isLoading,
|
||||||
|
bool showToolbarAsExpanded,
|
||||||
SecurityState securityInfoState,
|
SecurityState securityInfoState,
|
||||||
HistoryState historyState,
|
HistoryState historyState,
|
||||||
ReaderableState readerableState,
|
ReaderableState readerableState,
|
||||||
@@ -97,6 +100,10 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
@override
|
@override
|
||||||
TabState isLoading(bool isLoading) => call(isLoading: isLoading);
|
TabState isLoading(bool isLoading) => call(isLoading: isLoading);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState showToolbarAsExpanded(bool showToolbarAsExpanded) =>
|
||||||
|
call(showToolbarAsExpanded: showToolbarAsExpanded);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TabState securityInfoState(SecurityState securityInfoState) =>
|
TabState securityInfoState(SecurityState securityInfoState) =>
|
||||||
call(securityInfoState: securityInfoState);
|
call(securityInfoState: securityInfoState);
|
||||||
@@ -132,6 +139,7 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
Object? isPrivate = const $CopyWithPlaceholder(),
|
Object? isPrivate = const $CopyWithPlaceholder(),
|
||||||
Object? isFullScreen = const $CopyWithPlaceholder(),
|
Object? isFullScreen = const $CopyWithPlaceholder(),
|
||||||
Object? isLoading = const $CopyWithPlaceholder(),
|
Object? isLoading = const $CopyWithPlaceholder(),
|
||||||
|
Object? showToolbarAsExpanded = const $CopyWithPlaceholder(),
|
||||||
Object? securityInfoState = const $CopyWithPlaceholder(),
|
Object? securityInfoState = const $CopyWithPlaceholder(),
|
||||||
Object? historyState = const $CopyWithPlaceholder(),
|
Object? historyState = const $CopyWithPlaceholder(),
|
||||||
Object? readerableState = const $CopyWithPlaceholder(),
|
Object? readerableState = const $CopyWithPlaceholder(),
|
||||||
@@ -180,6 +188,12 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
? _value.isLoading
|
? _value.isLoading
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: isLoading as bool,
|
: isLoading as bool,
|
||||||
|
showToolbarAsExpanded:
|
||||||
|
showToolbarAsExpanded == const $CopyWithPlaceholder() ||
|
||||||
|
showToolbarAsExpanded == null
|
||||||
|
? _value.showToolbarAsExpanded
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: showToolbarAsExpanded as bool,
|
||||||
securityInfoState:
|
securityInfoState:
|
||||||
securityInfoState == const $CopyWithPlaceholder() ||
|
securityInfoState == const $CopyWithPlaceholder() ||
|
||||||
securityInfoState == null
|
securityInfoState == null
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class TabStates extends _$TabStates {
|
|||||||
isPrivate: contentState.isPrivate,
|
isPrivate: contentState.isPrivate,
|
||||||
isFullScreen: contentState.isFullScreen,
|
isFullScreen: contentState.isFullScreen,
|
||||||
isLoading: contentState.isLoading,
|
isLoading: contentState.isLoading,
|
||||||
|
showToolbarAsExpanded: contentState.showToolbarAsExpanded,
|
||||||
);
|
);
|
||||||
|
|
||||||
_updateState({...state}..[contentState.id] = newState);
|
_updateState({...state}..[contentState.id] = newState);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class TabStatesProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$tabStatesHash() => r'985d2eb87c59c3a113b8bbcf9d96267c42a2f543';
|
String _$tabStatesHash() => r'975de1242f42e2017754dc560125788b83ad8b72';
|
||||||
|
|
||||||
abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
|
abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
|
||||||
Map<String, TabState> build();
|
Map<String, TabState> build();
|
||||||
|
|||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2024-2026 Fabian Freund.
|
|
||||||
*
|
|
||||||
* This file is part of WebLibre
|
|
||||||
* (see https://weblibre.eu).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'tab_bar_dismissable.g.dart';
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
class TabBarDismissableController extends _$TabBarDismissableController {
|
|
||||||
void show() {
|
|
||||||
state = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dismiss() {
|
|
||||||
if (!state) {
|
|
||||||
state = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool build() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'tab_bar_dismissable.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// ignore_for_file: type=lint, type=warning
|
|
||||||
|
|
||||||
@ProviderFor(TabBarDismissableController)
|
|
||||||
final tabBarDismissableControllerProvider =
|
|
||||||
TabBarDismissableControllerProvider._();
|
|
||||||
|
|
||||||
final class TabBarDismissableControllerProvider
|
|
||||||
extends $NotifierProvider<TabBarDismissableController, bool> {
|
|
||||||
TabBarDismissableControllerProvider._()
|
|
||||||
: super(
|
|
||||||
from: null,
|
|
||||||
argument: null,
|
|
||||||
retry: null,
|
|
||||||
name: r'tabBarDismissableControllerProvider',
|
|
||||||
isAutoDispose: false,
|
|
||||||
dependencies: null,
|
|
||||||
$allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String debugGetCreateSourceHash() => _$tabBarDismissableControllerHash();
|
|
||||||
|
|
||||||
@$internal
|
|
||||||
@override
|
|
||||||
TabBarDismissableController create() => TabBarDismissableController();
|
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
|
||||||
Override overrideWithValue(bool value) {
|
|
||||||
return $ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
providerOverride: $SyncValueProvider<bool>(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _$tabBarDismissableControllerHash() =>
|
|
||||||
r'e0c2f700ccebc35bc8029370cdebd63e98bf95e1';
|
|
||||||
|
|
||||||
abstract class _$TabBarDismissableController extends $Notifier<bool> {
|
|
||||||
bool build();
|
|
||||||
@$mustCallSuper
|
|
||||||
@override
|
|
||||||
void runBuild() {
|
|
||||||
final ref = this.ref as $Ref<bool, bool>;
|
|
||||||
final element =
|
|
||||||
ref.element
|
|
||||||
as $ClassProviderElement<
|
|
||||||
AnyNotifier<bool, bool>,
|
|
||||||
bool,
|
|
||||||
Object?,
|
|
||||||
Object?
|
|
||||||
>;
|
|
||||||
element.handleCreate(ref, build);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2026 Fabian Freund.
|
||||||
|
*
|
||||||
|
* This file is part of WebLibre
|
||||||
|
* (see https://weblibre.eu).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import 'package:riverpod/riverpod.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
|
|
||||||
|
part 'toolbar_visibility.g.dart';
|
||||||
|
|
||||||
|
enum ToolbarVisibility { visible, hidden, dismissed }
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class ToolbarVisibilityController extends _$ToolbarVisibilityController {
|
||||||
|
@override
|
||||||
|
ToolbarVisibility build(String? tabId) {
|
||||||
|
// Show toolbar when loading starts
|
||||||
|
ref.listen(tabStatesProvider.select((tabs) => tabs[tabId]?.isLoading), (
|
||||||
|
previous,
|
||||||
|
next,
|
||||||
|
) {
|
||||||
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (next == true) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show toolbar on navigation (history state change)
|
||||||
|
ref.listen(tabStatesProvider.select((tabs) => tabs[tabId]?.historyState), (
|
||||||
|
previous,
|
||||||
|
next,
|
||||||
|
) {
|
||||||
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (next != null && previous != null && previous != next) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force-show when GeckoView requests toolbar expansion
|
||||||
|
// (e.g. touch on form input)
|
||||||
|
ref.listen<bool>(
|
||||||
|
tabStatesProvider.select(
|
||||||
|
(tabs) => tabs[tabId]?.showToolbarAsExpanded ?? false,
|
||||||
|
),
|
||||||
|
(previous, next) {
|
||||||
|
if (next && previous != next) {
|
||||||
|
forceShow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return ToolbarVisibility.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hide toolbar via scroll. All guards checked internally.
|
||||||
|
void requestHide() {
|
||||||
|
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
||||||
|
if (!settings.autoHideTabBar) return;
|
||||||
|
|
||||||
|
final isLoading = ref.read(tabStatesProvider)[tabId]?.isLoading ?? false;
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
final viewportService = ref.read(viewportServiceProvider);
|
||||||
|
if (!viewportService.isBrowserHandlingScrollEnabled) return;
|
||||||
|
|
||||||
|
if (state == ToolbarVisibility.dismissed) return;
|
||||||
|
|
||||||
|
state = ToolbarVisibility.hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show toolbar (scroll-up, tab change, loading start, etc).
|
||||||
|
/// Won't show if manually dismissed — use forceShow() for that.
|
||||||
|
void show() {
|
||||||
|
if (state == ToolbarVisibility.dismissed) return;
|
||||||
|
state = ToolbarVisibility.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Force-show unconditionally + un-dismiss.
|
||||||
|
void forceShow() {
|
||||||
|
state = ToolbarVisibility.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dismiss toolbar (user swipe). Only affects this tab.
|
||||||
|
void dismiss() {
|
||||||
|
state = ToolbarVisibility.dismissed;
|
||||||
|
}
|
||||||
|
}
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'toolbar_visibility.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(ToolbarVisibilityController)
|
||||||
|
final toolbarVisibilityControllerProvider =
|
||||||
|
ToolbarVisibilityControllerFamily._();
|
||||||
|
|
||||||
|
final class ToolbarVisibilityControllerProvider
|
||||||
|
extends $NotifierProvider<ToolbarVisibilityController, ToolbarVisibility> {
|
||||||
|
ToolbarVisibilityControllerProvider._({
|
||||||
|
required ToolbarVisibilityControllerFamily super.from,
|
||||||
|
required String? super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'toolbarVisibilityControllerProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$toolbarVisibilityControllerHash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'toolbarVisibilityControllerProvider'
|
||||||
|
''
|
||||||
|
'($argument)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
ToolbarVisibilityController create() => ToolbarVisibilityController();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ToolbarVisibility value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ToolbarVisibility>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ToolbarVisibilityControllerProvider &&
|
||||||
|
other.argument == argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return argument.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$toolbarVisibilityControllerHash() =>
|
||||||
|
r'242449893afd6780d977544659e83d4641c8a0ad';
|
||||||
|
|
||||||
|
final class ToolbarVisibilityControllerFamily extends $Family
|
||||||
|
with
|
||||||
|
$ClassFamilyOverride<
|
||||||
|
ToolbarVisibilityController,
|
||||||
|
ToolbarVisibility,
|
||||||
|
ToolbarVisibility,
|
||||||
|
ToolbarVisibility,
|
||||||
|
String?
|
||||||
|
> {
|
||||||
|
ToolbarVisibilityControllerFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'toolbarVisibilityControllerProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
ToolbarVisibilityControllerProvider call(String? tabId) =>
|
||||||
|
ToolbarVisibilityControllerProvider._(argument: tabId, from: this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => r'toolbarVisibilityControllerProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$ToolbarVisibilityController
|
||||||
|
extends $Notifier<ToolbarVisibility> {
|
||||||
|
late final _$args = ref.$arg as String?;
|
||||||
|
String? get tabId => _$args;
|
||||||
|
|
||||||
|
ToolbarVisibility build(String? tabId);
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final ref = this.ref as $Ref<ToolbarVisibility, ToolbarVisibility>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<ToolbarVisibility, ToolbarVisibility>,
|
||||||
|
ToolbarVisibility,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleCreate(ref, () => build(_$args));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
|||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_bar_dismissable.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/toolbar_visibility.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/keep_tab_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/keep_tab_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart';
|
||||||
@@ -136,7 +136,6 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
final bool showMainToolbar;
|
final bool showMainToolbar;
|
||||||
final bool showContextualToolbar;
|
final bool showContextualToolbar;
|
||||||
final bool showQuickTabSwitcherBar;
|
final bool showQuickTabSwitcherBar;
|
||||||
final ValueNotifier<bool> displayAppBar;
|
|
||||||
final Stream<Offset>? pointerMoveEvents;
|
final Stream<Offset>? pointerMoveEvents;
|
||||||
final TabBarPosition tabBarPosition;
|
final TabBarPosition tabBarPosition;
|
||||||
|
|
||||||
@@ -144,7 +143,6 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
required this.showMainToolbar,
|
required this.showMainToolbar,
|
||||||
required this.showContextualToolbar,
|
required this.showContextualToolbar,
|
||||||
required this.showQuickTabSwitcherBar,
|
required this.showQuickTabSwitcherBar,
|
||||||
required this.displayAppBar,
|
|
||||||
required this.tabBarPosition,
|
required this.tabBarPosition,
|
||||||
required this.pointerMoveEvents,
|
required this.pointerMoveEvents,
|
||||||
});
|
});
|
||||||
@@ -154,30 +152,34 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
final tabId = ref.watch(selectedTabProvider);
|
final tabId = ref.watch(selectedTabProvider);
|
||||||
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||||
|
|
||||||
final autoHideTabBar = ref.watch(
|
|
||||||
generalSettingsWithDefaultsProvider.select(
|
|
||||||
(value) => value.autoHideTabBar,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Auto-hide scroll detection hooks (run unconditionally per hook rules)
|
// Auto-hide scroll detection hooks (run unconditionally per hook rules)
|
||||||
final diffAcc = useRef(0.0);
|
final diffAcc = useRef(0.0);
|
||||||
|
|
||||||
void resetHiddenState() {
|
// Reset scroll accumulator on tab switch
|
||||||
if (!ref.read(tabBarDismissableControllerProvider)) {
|
useEffect(() {
|
||||||
displayAppBar.value = true;
|
diffAcc.value = 0.0;
|
||||||
|
return null;
|
||||||
|
}, [tabId]);
|
||||||
|
|
||||||
|
// Reset scroll accumulator when controller shows toolbar
|
||||||
|
// (covers: loading start, navigation, showToolbarAsExpanded, forceShow)
|
||||||
|
ref.listen(toolbarVisibilityControllerProvider(tabId), (previous, next) {
|
||||||
|
if (next == ToolbarVisibility.visible && previous != next) {
|
||||||
|
diffAcc.value = 0.0;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
void resetHiddenState() {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
toolbarVisibilityControllerProvider(
|
||||||
|
ref.read(selectedTabProvider),
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
|
.show();
|
||||||
diffAcc.value = 0.0;
|
diffAcc.value = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
if (!autoHideTabBar) return null;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
resetHiddenState();
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}, [tabId, autoHideTabBar]);
|
|
||||||
|
|
||||||
useOnAppLifecycleStateChange((previous, current) {
|
useOnAppLifecycleStateChange((previous, current) {
|
||||||
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
||||||
if (current == AppLifecycleState.resumed) {
|
if (current == AppLifecycleState.resumed) {
|
||||||
@@ -185,38 +187,13 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.listen(tabStateProvider(tabId).select((value) => value?.isLoading), (
|
|
||||||
previous,
|
|
||||||
next,
|
|
||||||
) {
|
|
||||||
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
|
||||||
if (next == true) {
|
|
||||||
resetHiddenState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ref.listen(tabStateProvider(tabId).select((value) => value?.historyState), (
|
|
||||||
previous,
|
|
||||||
next,
|
|
||||||
) {
|
|
||||||
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
|
||||||
if (next != null && previous != null) {
|
|
||||||
if (previous != next) {
|
|
||||||
resetHiddenState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useOnStreamChange(
|
useOnStreamChange(
|
||||||
pointerMoveEvents,
|
pointerMoveEvents,
|
||||||
onData: (event) {
|
onData: (event) {
|
||||||
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) {
|
final autoHide = ref
|
||||||
return;
|
.read(generalSettingsWithDefaultsProvider)
|
||||||
}
|
.autoHideTabBar;
|
||||||
// Don't apply auto-hide when toolbar is manually dismissed
|
if (!autoHide) return;
|
||||||
if (ref.read(tabBarDismissableControllerProvider)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final diff = event.dy;
|
final diff = event.dy;
|
||||||
if (diff < 0) {
|
if (diff < 0) {
|
||||||
@@ -225,7 +202,13 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
diffAcc.value += diff;
|
diffAcc.value += diff;
|
||||||
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
|
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
|
||||||
displayAppBar.value = false;
|
ref
|
||||||
|
.read(
|
||||||
|
toolbarVisibilityControllerProvider(
|
||||||
|
ref.read(selectedTabProvider),
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
|
.requestHide();
|
||||||
}
|
}
|
||||||
} else if (diff > 0) {
|
} else if (diff > 0) {
|
||||||
if (diffAcc.value < 0) {
|
if (diffAcc.value < 0) {
|
||||||
@@ -267,6 +250,9 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
final tabInFullScreen = ref.watch(
|
final tabInFullScreen = ref.watch(
|
||||||
selectedTabStateProvider.select((value) => value?.isFullScreen ?? false),
|
selectedTabStateProvider.select((value) => value?.isFullScreen ?? false),
|
||||||
);
|
);
|
||||||
|
final tabIsLoading = ref.watch(
|
||||||
|
selectedTabStateProvider.select((value) => value?.isLoading ?? false),
|
||||||
|
);
|
||||||
|
|
||||||
final overlayController = useOverlayPortalController();
|
final overlayController = useOverlayPortalController();
|
||||||
|
|
||||||
@@ -300,12 +286,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final displayAppBar = useValueNotifier(true);
|
|
||||||
|
|
||||||
ref.listen(tabBarDismissableControllerProvider, (previous, next) {
|
|
||||||
displayAppBar.value = !next;
|
|
||||||
});
|
|
||||||
|
|
||||||
ref.listen(overlayControllerProvider, (previous, next) {
|
ref.listen(overlayControllerProvider, (previous, next) {
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
overlayController.show();
|
overlayController.show();
|
||||||
@@ -330,14 +310,19 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
final sheetDisplayed = displayedSheet != null;
|
final sheetDisplayed = displayedSheet != null;
|
||||||
|
|
||||||
// Compute visibility states for toolbars
|
// Compute visibility states for toolbars
|
||||||
final appBarVisible = useValueListenable(displayAppBar);
|
final selectedTabId = ref.watch(selectedTabProvider);
|
||||||
|
final toolbarState = ref.watch(
|
||||||
|
toolbarVisibilityControllerProvider(selectedTabId),
|
||||||
|
);
|
||||||
|
|
||||||
// Toolbar is visible when: sheet is shown OR (not fullscreen AND app bar visible)
|
// Toolbar is visible when: sheet is shown OR (not fullscreen AND controller says visible)
|
||||||
// appBarVisible reflects both scroll-based auto-hide and swipe dismiss state
|
// The controller handles loading-start show internally via ref.listen on isLoading
|
||||||
|
final effectiveAppBarVisible =
|
||||||
|
toolbarState == ToolbarVisibility.visible;
|
||||||
final topToolbarVisible =
|
final topToolbarVisible =
|
||||||
sheetDisplayed || (!tabInFullScreen && appBarVisible);
|
sheetDisplayed || (!tabInFullScreen && effectiveAppBarVisible);
|
||||||
final bottomToolbarVisible =
|
final bottomToolbarVisible =
|
||||||
sheetDisplayed || (!tabInFullScreen && appBarVisible);
|
sheetDisplayed || (!tabInFullScreen && effectiveAppBarVisible);
|
||||||
|
|
||||||
// Calculate relative safe area for sheet max size
|
// Calculate relative safe area for sheet max size
|
||||||
final relativeSafeArea = MediaQuery.of(context).relativeSafeArea();
|
final relativeSafeArea = MediaQuery.of(context).relativeSafeArea();
|
||||||
@@ -382,7 +367,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
// Watch find-in-page visibility for the selected tab
|
// Watch find-in-page visibility for the selected tab
|
||||||
final selectedTabId = ref.watch(selectedTabProvider);
|
|
||||||
final findInPageVisible =
|
final findInPageVisible =
|
||||||
selectedTabId != null &&
|
selectedTabId != null &&
|
||||||
ref.watch(
|
ref.watch(
|
||||||
@@ -446,7 +430,8 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
// Keep clipping state in sync with keyboard and auto-hide states.
|
// Keep clipping state in sync with keyboard and auto-hide states.
|
||||||
// While keyboard is visible, do not apply toolbar clipping.
|
// While keyboard is visible, do not apply toolbar clipping.
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final targetClippingPx = autoHideTabBar && !keyboardVisible
|
final targetClippingPx =
|
||||||
|
autoHideTabBar && !keyboardVisible && !tabIsLoading
|
||||||
? desiredToolbarClippingPx.value
|
? desiredToolbarClippingPx.value
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
@@ -456,7 +441,7 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [autoHideTabBar, keyboardVisible]);
|
}, [autoHideTabBar, keyboardVisible, tabIsLoading]);
|
||||||
|
|
||||||
final animationProgressCallback = useCallback((
|
final animationProgressCallback = useCallback((
|
||||||
double progress,
|
double progress,
|
||||||
@@ -467,13 +452,15 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
final clippingPx = -((1.0 - progress) * heightPx * pixelRatio).round();
|
final clippingPx = -((1.0 - progress) * heightPx * pixelRatio).round();
|
||||||
desiredToolbarClippingPx.value = clippingPx;
|
desiredToolbarClippingPx.value = clippingPx;
|
||||||
|
|
||||||
final targetClippingPx = keyboardVisible ? 0 : clippingPx;
|
final targetClippingPx = (keyboardVisible || tabIsLoading)
|
||||||
|
? 0
|
||||||
|
: clippingPx;
|
||||||
|
|
||||||
if (targetClippingPx != lastClippingPx.value) {
|
if (targetClippingPx != lastClippingPx.value) {
|
||||||
lastClippingPx.value = targetClippingPx;
|
lastClippingPx.value = targetClippingPx;
|
||||||
unawaited(viewportService.setVerticalClipping(targetClippingPx));
|
unawaited(viewportService.setVerticalClipping(targetClippingPx));
|
||||||
}
|
}
|
||||||
}, [keyboardVisible, pixelRatio]);
|
}, [keyboardVisible, pixelRatio, tabIsLoading]);
|
||||||
|
|
||||||
// Theme with dynamic snackbar margin to position above bottom toolbar
|
// Theme with dynamic snackbar margin to position above bottom toolbar
|
||||||
final themeData = Theme.of(context).copyWith(
|
final themeData = Theme.of(context).copyWith(
|
||||||
@@ -522,7 +509,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
bottom: browserBottomOffset,
|
bottom: browserBottomOffset,
|
||||||
child: _Browser(
|
child: _Browser(
|
||||||
overlayController: overlayController,
|
overlayController: overlayController,
|
||||||
displayAppBar: displayAppBar,
|
|
||||||
tabInFullScreen: tabInFullScreen,
|
tabInFullScreen: tabInFullScreen,
|
||||||
pointerMoveEventSink: pointerMoveEventsController.sink,
|
pointerMoveEventSink: pointerMoveEventsController.sink,
|
||||||
sheetDisplayed: sheetDisplayed,
|
sheetDisplayed: sheetDisplayed,
|
||||||
@@ -558,7 +544,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
child: _TabBar(
|
child: _TabBar(
|
||||||
tabBarPosition: TabBarPosition.bottom,
|
tabBarPosition: TabBarPosition.bottom,
|
||||||
displayAppBar: displayAppBar,
|
|
||||||
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
||||||
showContextualToolbar: showContextualToolbar,
|
showContextualToolbar: showContextualToolbar,
|
||||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
@@ -583,7 +568,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
child: _TabBar(
|
child: _TabBar(
|
||||||
tabBarPosition: TabBarPosition.top,
|
tabBarPosition: TabBarPosition.top,
|
||||||
showMainToolbar: true,
|
showMainToolbar: true,
|
||||||
displayAppBar: displayAppBar,
|
|
||||||
showContextualToolbar: showContextualToolbar,
|
showContextualToolbar: showContextualToolbar,
|
||||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
pointerMoveEvents: pointerMoveEventsController.stream,
|
pointerMoveEvents: pointerMoveEventsController.stream,
|
||||||
@@ -729,7 +713,6 @@ class _Browser extends HookConsumerWidget {
|
|||||||
Duration get _backButtonPressTimeout => const Duration(seconds: 2);
|
Duration get _backButtonPressTimeout => const Duration(seconds: 2);
|
||||||
|
|
||||||
final OverlayPortalController overlayController;
|
final OverlayPortalController overlayController;
|
||||||
final ValueNotifier<bool> displayAppBar;
|
|
||||||
final StreamSink<Offset> pointerMoveEventSink;
|
final StreamSink<Offset> pointerMoveEventSink;
|
||||||
final bool tabInFullScreen;
|
final bool tabInFullScreen;
|
||||||
final bool sheetDisplayed;
|
final bool sheetDisplayed;
|
||||||
@@ -737,7 +720,6 @@ class _Browser extends HookConsumerWidget {
|
|||||||
|
|
||||||
const _Browser({
|
const _Browser({
|
||||||
required this.overlayController,
|
required this.overlayController,
|
||||||
required this.displayAppBar,
|
|
||||||
required this.tabInFullScreen,
|
required this.tabInFullScreen,
|
||||||
required this.pointerMoveEventSink,
|
required this.pointerMoveEventSink,
|
||||||
required this.sheetDisplayed,
|
required this.sheetDisplayed,
|
||||||
@@ -822,9 +804,10 @@ class _Browser extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Make sure app bar is visible
|
//Make sure app bar is visible
|
||||||
if (!ref.read(tabBarDismissableControllerProvider)) {
|
final tabId = ref.read(selectedTabProvider);
|
||||||
displayAppBar.value = true;
|
ref
|
||||||
}
|
.read(toolbarVisibilityControllerProvider(tabId).notifier)
|
||||||
|
.show();
|
||||||
|
|
||||||
if (tabState?.isLoading == true) {
|
if (tabState?.isLoading == true) {
|
||||||
lastBackButtonPress.value = null;
|
lastBackButtonPress.value = null;
|
||||||
|
|||||||
+26
-6
@@ -32,12 +32,13 @@ import 'package:weblibre/core/routing/routes.dart';
|
|||||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_bar_dismissable.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/toolbar_visibility.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
||||||
@@ -59,6 +60,7 @@ import 'package:weblibre/presentation/icons/weblibre_icons.dart';
|
|||||||
import 'package:weblibre/presentation/widgets/safe_raw_image.dart';
|
import 'package:weblibre/presentation/widgets/safe_raw_image.dart';
|
||||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||||
|
import 'package:weblibre/utils/ui_helper.dart';
|
||||||
|
|
||||||
class BrowserTopAppBar extends HookConsumerWidget {
|
class BrowserTopAppBar extends HookConsumerWidget {
|
||||||
final bool showMainToolbar;
|
final bool showMainToolbar;
|
||||||
@@ -68,7 +70,8 @@ class BrowserTopAppBar extends HookConsumerWidget {
|
|||||||
late final BrowserTabBar _tabBar;
|
late final BrowserTabBar _tabBar;
|
||||||
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
||||||
|
|
||||||
BrowserTopAppBar({super.key,
|
BrowserTopAppBar({
|
||||||
|
super.key,
|
||||||
required this.showMainToolbar,
|
required this.showMainToolbar,
|
||||||
required this.showContextualToolbar,
|
required this.showContextualToolbar,
|
||||||
required this.showQuickTabSwitcherBar,
|
required this.showQuickTabSwitcherBar,
|
||||||
@@ -103,7 +106,8 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
|||||||
late final BrowserTabBar _tabBar;
|
late final BrowserTabBar _tabBar;
|
||||||
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
||||||
|
|
||||||
BrowserBottomAppBar({super.key,
|
BrowserBottomAppBar({
|
||||||
|
super.key,
|
||||||
required this.showMainToolbar,
|
required this.showMainToolbar,
|
||||||
required this.displayedSheet,
|
required this.displayedSheet,
|
||||||
required this.showContextualToolbar,
|
required this.showContextualToolbar,
|
||||||
@@ -148,7 +152,8 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
final bool showMainToolbarNavigationButton;
|
final bool showMainToolbarNavigationButton;
|
||||||
final bool showMainToolbarTabActionButton;
|
final bool showMainToolbarTabActionButton;
|
||||||
|
|
||||||
const BrowserTabBar({super.key,
|
const BrowserTabBar({
|
||||||
|
super.key,
|
||||||
required this.showMainToolbar,
|
required this.showMainToolbar,
|
||||||
required this.displayedSheet,
|
required this.displayedSheet,
|
||||||
required this.showContextualToolbar,
|
required this.showContextualToolbar,
|
||||||
@@ -270,8 +275,23 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
!distance.dy.isNegative && distance.dy.abs() > dismissThreshold,
|
!distance.dy.isNegative && distance.dy.abs() > dismissThreshold,
|
||||||
};
|
};
|
||||||
if (shouldDismiss && ref.read(bottomSheetControllerProvider) == null) {
|
if (shouldDismiss && ref.read(bottomSheetControllerProvider) == null) {
|
||||||
unawaited(HapticFeedback.lightImpact());
|
final viewportService = ref.read(viewportServiceProvider);
|
||||||
ref.read(tabBarDismissableControllerProvider.notifier).dismiss();
|
if (viewportService.isBrowserHandlingScrollEnabled) {
|
||||||
|
unawaited(HapticFeedback.lightImpact());
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
toolbarVisibilityControllerProvider(selectedTabId).notifier,
|
||||||
|
)
|
||||||
|
.dismiss();
|
||||||
|
} else if (context.mounted) {
|
||||||
|
showDismissOverrideMessage(context, () {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
toolbarVisibilityControllerProvider(selectedTabId).notifier,
|
||||||
|
)
|
||||||
|
.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
+12
-4
@@ -21,8 +21,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_bar_dismissable.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/toolbar_visibility.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/readerview/domain/providers/readerable.dart';
|
import 'package:weblibre/features/geckoview/features/readerview/domain/providers/readerable.dart';
|
||||||
|
|
||||||
class BrowserFab extends HookConsumerWidget {
|
class BrowserFab extends HookConsumerWidget {
|
||||||
@@ -30,7 +31,10 @@ class BrowserFab extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tabBarDismissed = ref.watch(tabBarDismissableControllerProvider);
|
final selectedTabId = ref.watch(selectedTabProvider);
|
||||||
|
final toolbarState = ref.watch(
|
||||||
|
toolbarVisibilityControllerProvider(selectedTabId),
|
||||||
|
);
|
||||||
|
|
||||||
final appearanceButtonVisible = ref.watch(
|
final appearanceButtonVisible = ref.watch(
|
||||||
appearanceButtonVisibilityProvider.select(
|
appearanceButtonVisibilityProvider.select(
|
||||||
@@ -54,12 +58,16 @@ class BrowserFab extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: const Icon(MdiIcons.formatFont),
|
child: const Icon(MdiIcons.formatFont),
|
||||||
);
|
);
|
||||||
} else if (tabBarDismissed) {
|
} else if (toolbarState == ToolbarVisibility.dismissed) {
|
||||||
child = FloatingActionButton(
|
child = FloatingActionButton(
|
||||||
key: const ValueKey('dock_fab'),
|
key: const ValueKey('dock_fab'),
|
||||||
heroTag: 'dock_fab',
|
heroTag: 'dock_fab',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.read(tabBarDismissableControllerProvider.notifier).show();
|
ref
|
||||||
|
.read(
|
||||||
|
toolbarVisibilityControllerProvider(selectedTabId).notifier,
|
||||||
|
)
|
||||||
|
.forceShow();
|
||||||
},
|
},
|
||||||
child: const Icon(MdiIcons.dockBottom),
|
child: const Icon(MdiIcons.dockBottom),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ final class ProfileRepositoryProvider
|
|||||||
ProfileRepository create() => ProfileRepository();
|
ProfileRepository create() => ProfileRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$profileRepositoryHash() => r'c17702af1e59727ab3fec26e9ca659048e92c8bb';
|
String _$profileRepositoryHash() => r'b770e7406e1602f808cc8076c1eda67b4fce6b2d';
|
||||||
|
|
||||||
abstract class _$ProfileRepository extends $AsyncNotifier<List<Profile>> {
|
abstract class _$ProfileRepository extends $AsyncNotifier<List<Profile>> {
|
||||||
FutureOr<List<Profile>> build();
|
FutureOr<List<Profile>> build();
|
||||||
|
|||||||
@@ -192,3 +192,19 @@ void showTabUndoClose(
|
|||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showDismissOverrideMessage(
|
||||||
|
BuildContext context,
|
||||||
|
VoidCallback onDismiss, {
|
||||||
|
Duration duration = const Duration(seconds: 4),
|
||||||
|
}) {
|
||||||
|
ScaffoldMessenger.of(context).clearSnackBars();
|
||||||
|
|
||||||
|
final snackBar = _createFloatingSnackBar(
|
||||||
|
content: const Text('Hiding disabled by site'),
|
||||||
|
action: SnackBarAction(label: 'Dismiss', onPressed: onDismiss),
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
|||||||
+12
@@ -26,6 +26,7 @@ import eu.weblibre.flutter_mozilla_components.addons.WebExtensionActionPopupActi
|
|||||||
import eu.weblibre.flutter_mozilla_components.addons.WebExtensionPromptFeature
|
import eu.weblibre.flutter_mozilla_components.addons.WebExtensionPromptFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.databinding.FragmentBrowserBinding
|
import eu.weblibre.flutter_mozilla_components.databinding.FragmentBrowserBinding
|
||||||
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
||||||
|
import eu.weblibre.flutter_mozilla_components.feature.BrowserHandlingScrollFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.KeyboardVisibilityFeature
|
import eu.weblibre.flutter_mozilla_components.feature.KeyboardVisibilityFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
import eu.weblibre.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
||||||
@@ -97,6 +98,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
// Keyboard visibility detection feature
|
// Keyboard visibility detection feature
|
||||||
private var keyboardVisibilityFeature: KeyboardVisibilityFeature? = null
|
private var keyboardVisibilityFeature: KeyboardVisibilityFeature? = null
|
||||||
|
|
||||||
|
// Browser scroll-handling detection feature
|
||||||
|
private var browserHandlingScrollFeature: BrowserHandlingScrollFeature? = null
|
||||||
|
|
||||||
// Registers a photo picker activity launcher in single-select mode.
|
// Registers a photo picker activity launcher in single-select mode.
|
||||||
private val singleMediaPicker =
|
private val singleMediaPicker =
|
||||||
AndroidPhotoPicker.singleMediaPicker(
|
AndroidPhotoPicker.singleMediaPicker(
|
||||||
@@ -500,6 +504,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
keyboardVisibilityFeature = KeyboardVisibilityFeature(viewportEvents).also {
|
keyboardVisibilityFeature = KeyboardVisibilityFeature(viewportEvents).also {
|
||||||
it.start(binding.root)
|
it.start(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
browserHandlingScrollFeature = BrowserHandlingScrollFeature(viewportEvents).also {
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEngineSetupComplete()
|
onEngineSetupComplete()
|
||||||
@@ -614,6 +622,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
keyboardVisibilityFeature?.stop()
|
keyboardVisibilityFeature?.stop()
|
||||||
keyboardVisibilityFeature = null
|
keyboardVisibilityFeature = null
|
||||||
|
|
||||||
|
// Stop browser scroll-handling detection
|
||||||
|
browserHandlingScrollFeature?.stop()
|
||||||
|
browserHandlingScrollFeature = null
|
||||||
|
|
||||||
GlobalComponents.onPullToRefreshEnabledChanged = null
|
GlobalComponents.onPullToRefreshEnabledChanged = null
|
||||||
val engineView = fragmentEngineView
|
val engineView = fragmentEngineView
|
||||||
engineView?.setActivityContext(null)
|
engineView?.setActivityContext(null)
|
||||||
|
|||||||
+2
-1
@@ -216,7 +216,8 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
|||||||
progress = tab.content.progress.toLong(),
|
progress = tab.content.progress.toLong(),
|
||||||
isPrivate = tab.content.private,
|
isPrivate = tab.content.private,
|
||||||
isFullScreen = tab.content.fullScreen,
|
isFullScreen = tab.content.fullScreen,
|
||||||
isLoading = tab.content.loading
|
isLoading = tab.content.loading,
|
||||||
|
showToolbarAsExpanded = tab.content.showToolbarAsExpanded,
|
||||||
)
|
)
|
||||||
) { }
|
) { }
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-2
@@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import mozilla.components.browser.state.action.BrowserAction
|
import mozilla.components.browser.state.action.BrowserAction
|
||||||
|
import mozilla.components.browser.state.action.ContentAction
|
||||||
import mozilla.components.browser.state.selector.selectedTab
|
import mozilla.components.browser.state.selector.selectedTab
|
||||||
import mozilla.components.browser.state.state.BrowserState
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
import mozilla.components.feature.addons.logger
|
import mozilla.components.feature.addons.logger
|
||||||
@@ -188,7 +189,8 @@ class Events(
|
|||||||
it.content.private,
|
it.content.private,
|
||||||
it.content.fullScreen,
|
it.content.fullScreen,
|
||||||
it.content.progress,
|
it.content.progress,
|
||||||
it.content.loading
|
it.content.loading,
|
||||||
|
it.content.showToolbarAsExpanded,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.debounce(15)
|
.debounce(15)
|
||||||
@@ -204,9 +206,19 @@ class Events(
|
|||||||
progress = tab.content.progress.toLong(),
|
progress = tab.content.progress.toLong(),
|
||||||
isPrivate = tab.content.private,
|
isPrivate = tab.content.private,
|
||||||
isFullScreen = tab.content.fullScreen,
|
isFullScreen = tab.content.fullScreen,
|
||||||
isLoading = tab.content.loading
|
isLoading = tab.content.loading,
|
||||||
|
showToolbarAsExpanded = tab.content.showToolbarAsExpanded,
|
||||||
)
|
)
|
||||||
) { _ -> }
|
) { _ -> }
|
||||||
|
|
||||||
|
// Reset showToolbarAsExpanded after forwarding to Flutter,
|
||||||
|
// mirroring Fenix ToolbarBehaviorController behavior.
|
||||||
|
// This ensures subsequent expand events trigger a new state change.
|
||||||
|
if (tab.content.showToolbarAsExpanded) {
|
||||||
|
stateFlow.dispatch(
|
||||||
|
ContentAction.UpdateExpandedToolbarStateAction(tab.id, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.weblibre.flutter_mozilla_components.feature
|
||||||
|
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||||
|
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoViewportEvents
|
||||||
|
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature that reports whether GeckoView is currently handling scroll input.
|
||||||
|
*
|
||||||
|
* Checks InputResultDetail while active and only emits on state changes.
|
||||||
|
*/
|
||||||
|
class BrowserHandlingScrollFeature(
|
||||||
|
private val flutterEvents: GeckoViewportEvents,
|
||||||
|
) {
|
||||||
|
private var running = false
|
||||||
|
private var touchSessionActive = false
|
||||||
|
private var lastValue: Boolean? = null
|
||||||
|
private var touchListener: View.OnTouchListener? = null
|
||||||
|
private var touchTargetView: View? = null
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (running) return
|
||||||
|
running = true
|
||||||
|
touchSessionActive = false
|
||||||
|
lastValue = null
|
||||||
|
attachTouchListenerIfPossible()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (!running) return
|
||||||
|
running = false
|
||||||
|
touchSessionActive = false
|
||||||
|
detachTouchListenerIfPossible()
|
||||||
|
lastValue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun attachTouchListenerIfPossible() {
|
||||||
|
val engineViewRoot = GlobalComponents.components?.mainBrowserEngineView?.asView()
|
||||||
|
if (engineViewRoot == null) return
|
||||||
|
|
||||||
|
if (touchListener != null) return
|
||||||
|
|
||||||
|
val targetView = resolveTouchTargetView(engineViewRoot)
|
||||||
|
|
||||||
|
touchListener = View.OnTouchListener { _, event ->
|
||||||
|
if (!running) return@OnTouchListener false
|
||||||
|
|
||||||
|
when (event.actionMasked) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
touchSessionActive = true
|
||||||
|
emitIfChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
if (touchSessionActive) {
|
||||||
|
emitIfChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP,
|
||||||
|
MotionEvent.ACTION_CANCEL -> {
|
||||||
|
touchSessionActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never consume touch; GeckoView must keep handling it.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
targetView.setOnTouchListener(touchListener)
|
||||||
|
touchTargetView = targetView
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detachTouchListenerIfPossible() {
|
||||||
|
if (touchTargetView != null && touchListener != null) {
|
||||||
|
touchTargetView?.setOnTouchListener(null)
|
||||||
|
}
|
||||||
|
touchListener = null
|
||||||
|
touchTargetView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveTouchTargetView(root: View): View {
|
||||||
|
if (root !is ViewGroup) return root
|
||||||
|
|
||||||
|
// GeckoEngineView wraps the actual NestedGeckoView as its first child.
|
||||||
|
if (root.childCount > 0) {
|
||||||
|
return root.getChildAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitIfChanged() {
|
||||||
|
val isHandling = try {
|
||||||
|
val engineView = GlobalComponents.components?.mainBrowserEngineView
|
||||||
|
if (engineView == null) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val detail = engineView.getInputResultDetail()
|
||||||
|
detail.canScrollToTop() || detail.canScrollToBottom()
|
||||||
|
}
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastValue == isHandling) return
|
||||||
|
lastValue = isHandling
|
||||||
|
|
||||||
|
val sequence = EventSequence.next()
|
||||||
|
runOnUiThread {
|
||||||
|
flutterEvents.onBrowserHandlingScrollChanged(sequence, isHandling) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-2
@@ -1597,7 +1597,8 @@ data class TabContentState (
|
|||||||
val progress: Long,
|
val progress: Long,
|
||||||
val isPrivate: Boolean,
|
val isPrivate: Boolean,
|
||||||
val isFullScreen: Boolean,
|
val isFullScreen: Boolean,
|
||||||
val isLoading: Boolean
|
val isLoading: Boolean,
|
||||||
|
val showToolbarAsExpanded: Boolean
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
companion object {
|
companion object {
|
||||||
@@ -1611,7 +1612,8 @@ data class TabContentState (
|
|||||||
val isPrivate = pigeonVar_list[6] as Boolean
|
val isPrivate = pigeonVar_list[6] as Boolean
|
||||||
val isFullScreen = pigeonVar_list[7] as Boolean
|
val isFullScreen = pigeonVar_list[7] as Boolean
|
||||||
val isLoading = pigeonVar_list[8] as Boolean
|
val isLoading = pigeonVar_list[8] as Boolean
|
||||||
return TabContentState(id, parentId, contextId, url, title, progress, isPrivate, isFullScreen, isLoading)
|
val showToolbarAsExpanded = pigeonVar_list[9] as Boolean
|
||||||
|
return TabContentState(id, parentId, contextId, url, title, progress, isPrivate, isFullScreen, isLoading, showToolbarAsExpanded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
@@ -1625,6 +1627,7 @@ data class TabContentState (
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
isFullScreen,
|
isFullScreen,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
showToolbarAsExpanded,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -6919,6 +6922,31 @@ class GeckoViewportEvents(private val binaryMessenger: BinaryMessenger, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Called when GeckoView scroll-handling eligibility changes.
|
||||||
|
*
|
||||||
|
* [sequence] Event sequence number for ordering.
|
||||||
|
* [isHandling] True when browser content can consume scrolling for
|
||||||
|
* dynamic toolbar behavior. False when content is not scrollable or
|
||||||
|
* the page consumed touch input.
|
||||||
|
*/
|
||||||
|
fun onBrowserHandlingScrollChanged(sequenceArg: Long, isHandlingArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||||
|
{
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoViewportEvents.onBrowserHandlingScrollChanged$separatedMessageChannelSuffix"
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||||
|
channel.send(listOf(sequenceArg, isHandlingArg)) {
|
||||||
|
if (it is List<*>) {
|
||||||
|
if (it.size > 1) {
|
||||||
|
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||||
|
} else {
|
||||||
|
callback(Result.success(Unit))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface GeckoBookmarksApi {
|
interface GeckoBookmarksApi {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class GeckoViewportService extends GeckoViewportEvents {
|
|||||||
isVisible: false,
|
isVisible: false,
|
||||||
isAnimating: false,
|
isAnimating: false,
|
||||||
));
|
));
|
||||||
|
final _browserHandlingScrollSubject = BehaviorSubject<bool>.seeded(false);
|
||||||
|
|
||||||
/// Stream of keyboard visibility changes.
|
/// Stream of keyboard visibility changes.
|
||||||
///
|
///
|
||||||
@@ -46,6 +47,17 @@ class GeckoViewportService extends GeckoViewportEvents {
|
|||||||
/// Whether the keyboard is currently visible.
|
/// Whether the keyboard is currently visible.
|
||||||
bool get isKeyboardVisible => _keyboardSubject.value.isVisible;
|
bool get isKeyboardVisible => _keyboardSubject.value.isVisible;
|
||||||
|
|
||||||
|
/// Stream of browser scroll-handling eligibility.
|
||||||
|
///
|
||||||
|
/// Emits true when GeckoView reports that browser content can handle
|
||||||
|
/// scrolling for dynamic toolbar behavior.
|
||||||
|
ValueStream<bool> get browserHandlingScrollEvents =>
|
||||||
|
_browserHandlingScrollSubject.stream;
|
||||||
|
|
||||||
|
/// Current browser scroll-handling eligibility.
|
||||||
|
bool get isBrowserHandlingScrollEnabled =>
|
||||||
|
_browserHandlingScrollSubject.value;
|
||||||
|
|
||||||
/// Creates a new viewport service.
|
/// Creates a new viewport service.
|
||||||
///
|
///
|
||||||
/// Call [setUp] to register the event handlers after construction.
|
/// Call [setUp] to register the event handlers after construction.
|
||||||
@@ -113,8 +125,14 @@ class GeckoViewportService extends GeckoViewportEvents {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onBrowserHandlingScrollChanged(int sequence, bool isHandling) {
|
||||||
|
_browserHandlingScrollSubject.addWhenMoreRecent(sequence, null, isHandling);
|
||||||
|
}
|
||||||
|
|
||||||
/// Disposes the service and closes all streams.
|
/// Disposes the service and closes all streams.
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _keyboardSubject.close();
|
await _keyboardSubject.close();
|
||||||
|
await _browserHandlingScrollSubject.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1735,6 +1735,7 @@ class TabContentState {
|
|||||||
required this.isPrivate,
|
required this.isPrivate,
|
||||||
required this.isFullScreen,
|
required this.isFullScreen,
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
|
required this.showToolbarAsExpanded,
|
||||||
});
|
});
|
||||||
|
|
||||||
String id;
|
String id;
|
||||||
@@ -1755,6 +1756,8 @@ class TabContentState {
|
|||||||
|
|
||||||
bool isLoading;
|
bool isLoading;
|
||||||
|
|
||||||
|
bool showToolbarAsExpanded;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[
|
return <Object?>[
|
||||||
id,
|
id,
|
||||||
@@ -1766,6 +1769,7 @@ class TabContentState {
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
isFullScreen,
|
isFullScreen,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
showToolbarAsExpanded,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1784,6 +1788,7 @@ class TabContentState {
|
|||||||
isPrivate: result[6]! as bool,
|
isPrivate: result[6]! as bool,
|
||||||
isFullScreen: result[7]! as bool,
|
isFullScreen: result[7]! as bool,
|
||||||
isLoading: result[8]! as bool,
|
isLoading: result[8]! as bool,
|
||||||
|
showToolbarAsExpanded: result[9]! as bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8133,6 +8138,14 @@ abstract class GeckoViewportEvents {
|
|||||||
/// [isAnimating] Whether the keyboard is currently animating.
|
/// [isAnimating] Whether the keyboard is currently animating.
|
||||||
void onKeyboardVisibilityChanged(int sequence, int heightPx, bool isVisible, bool isAnimating);
|
void onKeyboardVisibilityChanged(int sequence, int heightPx, bool isVisible, bool isAnimating);
|
||||||
|
|
||||||
|
/// Called when GeckoView scroll-handling eligibility changes.
|
||||||
|
///
|
||||||
|
/// [sequence] Event sequence number for ordering.
|
||||||
|
/// [isHandling] True when browser content can consume scrolling for
|
||||||
|
/// dynamic toolbar behavior. False when content is not scrollable or
|
||||||
|
/// the page consumed touch input.
|
||||||
|
void onBrowserHandlingScrollChanged(int sequence, bool isHandling);
|
||||||
|
|
||||||
static void setUp(GeckoViewportEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
static void setUp(GeckoViewportEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
{
|
{
|
||||||
@@ -8169,6 +8182,34 @@ abstract class GeckoViewportEvents {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.flutter_mozilla_components.GeckoViewportEvents.onBrowserHandlingScrollChanged$messageChannelSuffix', pigeonChannelCodec,
|
||||||
|
binaryMessenger: binaryMessenger);
|
||||||
|
if (api == null) {
|
||||||
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
|
} else {
|
||||||
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
|
assert(message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoViewportEvents.onBrowserHandlingScrollChanged was null.');
|
||||||
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
|
final int? arg_sequence = (args[0] as int?);
|
||||||
|
assert(arg_sequence != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoViewportEvents.onBrowserHandlingScrollChanged was null, expected non-null int.');
|
||||||
|
final bool? arg_isHandling = (args[1] as bool?);
|
||||||
|
assert(arg_isHandling != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoViewportEvents.onBrowserHandlingScrollChanged was null, expected non-null bool.');
|
||||||
|
try {
|
||||||
|
api.onBrowserHandlingScrollChanged(arg_sequence!, arg_isHandling!);
|
||||||
|
return wrapResponse(empty: true);
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
return wrapResponse(error: e);
|
||||||
|
} catch (e) {
|
||||||
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ class TabContentState {
|
|||||||
final bool isPrivate;
|
final bool isPrivate;
|
||||||
final bool isFullScreen;
|
final bool isFullScreen;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
final bool showToolbarAsExpanded;
|
||||||
|
|
||||||
TabContentState(
|
TabContentState(
|
||||||
this.id,
|
this.id,
|
||||||
@@ -528,6 +529,7 @@ class TabContentState {
|
|||||||
this.isPrivate,
|
this.isPrivate,
|
||||||
this.isFullScreen,
|
this.isFullScreen,
|
||||||
this.isLoading,
|
this.isLoading,
|
||||||
|
this.showToolbarAsExpanded,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1746,6 +1748,14 @@ abstract class GeckoViewportEvents {
|
|||||||
bool isVisible,
|
bool isVisible,
|
||||||
bool isAnimating,
|
bool isAnimating,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Called when GeckoView scroll-handling eligibility changes.
|
||||||
|
///
|
||||||
|
/// [sequence] Event sequence number for ordering.
|
||||||
|
/// [isHandling] True when browser content can consume scrolling for
|
||||||
|
/// dynamic toolbar behavior. False when content is not scrollable or
|
||||||
|
/// the page consumed touch input.
|
||||||
|
void onBrowserHandlingScrollChanged(int sequence, bool isHandling);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user