quick tab switcher options hide hsitory; hide when no results;

This commit is contained in:
Fabian Freund
2026-02-27 14:04:21 +01:00
parent d25229cb95
commit 60838b446d
17 changed files with 477 additions and 58 deletions
@@ -27,8 +27,10 @@ import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
import 'package:weblibre/features/geckoview/features/search/domain/entities/tab_preview.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart';
@@ -37,6 +39,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
part 'providers.g.dart';
@@ -208,6 +211,82 @@ selectedContainerTabStatesWithContainer(Ref ref) {
]);
}
@Riverpod()
EquatableValue<List<TabStateWirthContainer>> quickTabSwitcherTabStates(
Ref ref,
QuickTabSwitcherMode mode,
) {
final selectedTabId = ref.watch(selectedTabProvider);
final tabStates = switch (mode) {
QuickTabSwitcherMode.lastUsedTabs => ref.watch(fifoTabStatesProvider).value,
QuickTabSwitcherMode.containerTabs =>
ref.watch(selectedContainerTabStatesWithContainerProvider).value,
};
return EquatableValue(
tabStates.where((state) => state.$1.id != selectedTabId).toList(),
);
}
@Riverpod()
Future<List<VisitInfo>> quickTabSwitcherHistorySuggestions(
Ref ref,
QuickTabSwitcherMode mode,
) async {
final showHistorySuggestions = ref.watch(
generalSettingsWithDefaultsProvider.select(
(settings) => settings.quickTabSwitcherShowHistorySuggestions,
),
);
if (!showHistorySuggestions) {
return [];
}
final hasTabStates = ref.watch(
quickTabSwitcherTabStatesProvider(
mode,
).select((value) => value.value.isNotEmpty),
);
if (hasTabStates) {
return [];
}
return ref
.read(historyRepositoryProvider.notifier)
.getVisitsPaginated(count: 25);
}
@Riverpod()
AsyncValue<bool> quickTabSwitcherHasResults(
Ref ref,
QuickTabSwitcherMode mode,
) {
final showQuickTabSwitcherBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
(settings) => settings.tabBarShowQuickTabSwitcherBar,
),
);
if (!showQuickTabSwitcherBar) {
return const AsyncValue.data(false);
}
final hasResults = ref.watch(
quickTabSwitcherTabStatesProvider(
mode,
).select((value) => value.value.isNotEmpty),
);
if (hasResults) {
return const AsyncValue.data(true);
}
return ref
.watch(quickTabSwitcherHistorySuggestionsProvider(mode))
.whenData((visits) => visits.isNotEmpty);
}
@Riverpod()
EquatableValue<List<TabEntity>> suggestedTabEntities(
Ref ref,
@@ -489,6 +489,267 @@ final class SelectedContainerTabStatesWithContainerProvider
String _$selectedContainerTabStatesWithContainerHash() =>
r'2fd5e12595b3aced1e235c9626073c34655d57fa';
@ProviderFor(quickTabSwitcherTabStates)
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
final class QuickTabSwitcherTabStatesProvider
extends
$FunctionalProvider<
EquatableValue<List<TabStateWirthContainer>>,
EquatableValue<List<TabStateWirthContainer>>,
EquatableValue<List<TabStateWirthContainer>>
>
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
QuickTabSwitcherTabStatesProvider._({
required QuickTabSwitcherTabStatesFamily super.from,
required QuickTabSwitcherMode super.argument,
}) : super(
retry: null,
name: r'quickTabSwitcherTabStatesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$quickTabSwitcherTabStatesHash();
@override
String toString() {
return r'quickTabSwitcherTabStatesProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
final argument = this.argument as QuickTabSwitcherMode;
return quickTabSwitcherTabStates(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(
EquatableValue<List<TabStateWirthContainer>> value,
) {
return $ProviderOverride(
origin: this,
providerOverride:
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
value,
),
);
}
@override
bool operator ==(Object other) {
return other is QuickTabSwitcherTabStatesProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$quickTabSwitcherTabStatesHash() =>
r'db82bf9e338c94768b0531aeaffb152ff726f81f';
final class QuickTabSwitcherTabStatesFamily extends $Family
with
$FunctionalFamilyOverride<
EquatableValue<List<TabStateWirthContainer>>,
QuickTabSwitcherMode
> {
QuickTabSwitcherTabStatesFamily._()
: super(
retry: null,
name: r'quickTabSwitcherTabStatesProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
QuickTabSwitcherTabStatesProvider call(QuickTabSwitcherMode mode) =>
QuickTabSwitcherTabStatesProvider._(argument: mode, from: this);
@override
String toString() => r'quickTabSwitcherTabStatesProvider';
}
@ProviderFor(quickTabSwitcherHistorySuggestions)
final quickTabSwitcherHistorySuggestionsProvider =
QuickTabSwitcherHistorySuggestionsFamily._();
final class QuickTabSwitcherHistorySuggestionsProvider
extends
$FunctionalProvider<
AsyncValue<List<VisitInfo>>,
List<VisitInfo>,
FutureOr<List<VisitInfo>>
>
with $FutureModifier<List<VisitInfo>>, $FutureProvider<List<VisitInfo>> {
QuickTabSwitcherHistorySuggestionsProvider._({
required QuickTabSwitcherHistorySuggestionsFamily super.from,
required QuickTabSwitcherMode super.argument,
}) : super(
retry: null,
name: r'quickTabSwitcherHistorySuggestionsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() =>
_$quickTabSwitcherHistorySuggestionsHash();
@override
String toString() {
return r'quickTabSwitcherHistorySuggestionsProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<List<VisitInfo>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<VisitInfo>> create(Ref ref) {
final argument = this.argument as QuickTabSwitcherMode;
return quickTabSwitcherHistorySuggestions(ref, argument);
}
@override
bool operator ==(Object other) {
return other is QuickTabSwitcherHistorySuggestionsProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$quickTabSwitcherHistorySuggestionsHash() =>
r'3446741dfedf6fda9ab61421c1b74370c7f43f5b';
final class QuickTabSwitcherHistorySuggestionsFamily extends $Family
with
$FunctionalFamilyOverride<
FutureOr<List<VisitInfo>>,
QuickTabSwitcherMode
> {
QuickTabSwitcherHistorySuggestionsFamily._()
: super(
retry: null,
name: r'quickTabSwitcherHistorySuggestionsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
QuickTabSwitcherHistorySuggestionsProvider call(QuickTabSwitcherMode mode) =>
QuickTabSwitcherHistorySuggestionsProvider._(argument: mode, from: this);
@override
String toString() => r'quickTabSwitcherHistorySuggestionsProvider';
}
@ProviderFor(quickTabSwitcherHasResults)
final quickTabSwitcherHasResultsProvider = QuickTabSwitcherHasResultsFamily._();
final class QuickTabSwitcherHasResultsProvider
extends
$FunctionalProvider<
AsyncValue<bool>,
AsyncValue<bool>,
AsyncValue<bool>
>
with $Provider<AsyncValue<bool>> {
QuickTabSwitcherHasResultsProvider._({
required QuickTabSwitcherHasResultsFamily super.from,
required QuickTabSwitcherMode super.argument,
}) : super(
retry: null,
name: r'quickTabSwitcherHasResultsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$quickTabSwitcherHasResultsHash();
@override
String toString() {
return r'quickTabSwitcherHasResultsProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<AsyncValue<bool>> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
AsyncValue<bool> create(Ref ref) {
final argument = this.argument as QuickTabSwitcherMode;
return quickTabSwitcherHasResults(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AsyncValue<bool> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AsyncValue<bool>>(value),
);
}
@override
bool operator ==(Object other) {
return other is QuickTabSwitcherHasResultsProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$quickTabSwitcherHasResultsHash() =>
r'bcbddfefe7f5eb2c5d8c7c1925fe593a857b477b';
final class QuickTabSwitcherHasResultsFamily extends $Family
with $FunctionalFamilyOverride<AsyncValue<bool>, QuickTabSwitcherMode> {
QuickTabSwitcherHasResultsFamily._()
: super(
retry: null,
name: r'quickTabSwitcherHasResultsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
QuickTabSwitcherHasResultsProvider call(QuickTabSwitcherMode mode) =>
QuickTabSwitcherHasResultsProvider._(argument: mode, from: this);
@override
String toString() => r'quickTabSwitcherHasResultsProvider';
}
@ProviderFor(suggestedTabEntities)
final suggestedTabEntitiesProvider = SuggestedTabEntitiesFamily._();
@@ -42,7 +42,7 @@ final class ProxySettingsReplicationProvider
}
String _$proxySettingsReplicationHash() =>
r'79eca77aa476ed46cfe342e52926ea1cf956dc70';
r'7f47a561441cc2d594e9380d62dcc31f4c1059d3';
abstract class _$ProxySettingsReplication extends $Notifier<void> {
void build();
@@ -48,4 +48,4 @@ final class ShowSiteSettingsBadgeProvider
}
String _$showSiteSettingsBadgeHash() =>
r'f44282c62556c39d439c499519a2a9861082cfcb';
r'c68e3b37b6f25e02779123b3f642a58dc98ba3a7';
@@ -39,6 +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/repositories/tab.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/presentation/controllers/tab_view_controllers.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/toolbar_visibility.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/keep_tab_dialog.dart';
@@ -274,6 +275,16 @@ class BrowserScreen extends HookConsumerWidget {
(value) => value.tabBarShowQuickTabSwitcherBar,
),
);
final quickTabSwitcherMode = ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.quickTabSwitcherMode,
),
);
final quickTabSwitcherHasResults = ref.watch(
quickTabSwitcherHasResultsProvider(quickTabSwitcherMode),
);
final displayQuickTabSwitcherBar =
showQuickTabSwitcherBar && (quickTabSwitcherHasResults.value ?? false);
final autoHideTabBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
@@ -373,7 +384,7 @@ class BrowserScreen extends HookConsumerWidget {
final bottomAppBarContentSize = BrowserBottomAppBar(
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
showContextualToolbar: showContextualToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
displayedSheet: displayedSheet,
).preferredSize;
// Total height includes safe area padding
@@ -391,7 +402,7 @@ class BrowserScreen extends HookConsumerWidget {
final topAppBarContentSize = BrowserTopAppBar(
showMainToolbar: tabBarPosition == TabBarPosition.top,
showContextualToolbar: showContextualToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
).preferredSize;
final topAppBarTotalHeight = topAppBarContentSize.height + topSafeArea;
@@ -608,7 +619,7 @@ class BrowserScreen extends HookConsumerWidget {
tabBarPosition: TabBarPosition.bottom,
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
showContextualToolbar: showContextualToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
// Only subscribe to scroll events when this is the active position
pointerMoveEvents: tabBarPosition == TabBarPosition.bottom
? pointerMoveEventsController.stream
@@ -631,7 +642,7 @@ class BrowserScreen extends HookConsumerWidget {
tabBarPosition: TabBarPosition.top,
showMainToolbar: true,
showContextualToolbar: showContextualToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
pointerMoveEvents: pointerMoveEventsController.stream,
),
),
@@ -47,7 +47,6 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/toolbar_button.dart';
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
@@ -55,7 +54,6 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/hooks/cached_future.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart';
import 'package:weblibre/presentation/icons/weblibre_icons.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
@@ -487,29 +485,49 @@ class QuickTabSwitcher extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final appColors = AppColors.of(context);
final selectedTabId = ref.watch(selectedTabProvider);
final showTitles = ref.watch(
generalSettingsWithDefaultsProvider.select(
(s) => s.quickTabSwitcherShowTitles,
),
);
final tabStates = ref.watch(
quickTabSwitcherTabStatesProvider(quickTabSwitcherMode),
);
final historySuggestions = ref
.watch(quickTabSwitcherHistorySuggestionsProvider(quickTabSwitcherMode))
.value;
final availableItems = tabStates.value
.map<_QuickTabItem>(
(state) => (
id: state.$1.id,
title: state.$1.titleOrAuthority,
tabMode: state.$1.tabMode,
isHistory: false,
url: state.$1.url,
color: state.$2?.color,
tabState: state.$1,
),
)
.followedBy(
(historySuggestions ?? []).map<_QuickTabItem>((state) {
final url = Uri.parse(state.url);
final tabStates = (switch (quickTabSwitcherMode) {
QuickTabSwitcherMode.lastUsedTabs => ref.watch(fifoTabStatesProvider),
QuickTabSwitcherMode.containerTabs => ref.watch(
selectedContainerTabStatesWithContainerProvider,
),
}).value.where((state) => state.$1.id != selectedTabId).toList();
return (
id: state.url,
title: state.title ?? url.authority,
tabMode: TabMode.regular,
isHistory: true,
url: url,
color: null,
tabState: null,
);
}),
)
.toList();
final historyAsync = useCachedFuture(() {
if (tabStates.isEmpty) {
return ref
.read(historyRepositoryProvider.notifier)
.getVisitsPaginated(count: 25);
}
return Future.value(<VisitInfo>[]);
}, [tabStates.isEmpty]);
if (availableItems.isEmpty) {
return const SizedBox.shrink();
}
final chipScrollController = useScrollController();
@@ -618,33 +636,7 @@ class QuickTabSwitcher extends HookConsumerWidget {
},
);
},
availableItems: tabStates
.map<_QuickTabItem>(
(state) => (
id: state.$1.id,
title: state.$1.titleOrAuthority,
tabMode: state.$1.tabMode,
isHistory: false,
url: state.$1.url,
color: state.$2?.color,
tabState: state.$1,
),
)
.followedBy(
(historyAsync.data ?? []).map<_QuickTabItem>((state) {
final url = Uri.parse(state.url);
return (
id: state.url,
title: state.title ?? url.authority,
tabMode: TabMode.regular,
isHistory: true,
url: url,
color: null,
tabState: null,
);
}),
),
availableItems: availableItems,
),
),
);
@@ -31,7 +31,6 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_sour
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
@DriftAccessor()
class SyncTabsResult {
final Set<String> deletedIsolationContextIds;
final int deletedCount;
@@ -42,6 +41,7 @@ class SyncTabsResult {
});
}
@DriftAccessor()
class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
final _undoHistory = <String, TabData>{};
Timer? _clearHistoryTimer;