rework tab bar; added contextual bar and quick switcher; tab abr dismissable; reorder now opt in; consolidated controllers and menu buttons;
This commit is contained in:
@@ -31,6 +31,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
|||||||
import 'package:weblibre/features/geckoview/features/search/domain/entities/tab_preview.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/container_filter.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.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/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||||
@@ -38,6 +39,8 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
|||||||
|
|
||||||
part 'providers.g.dart';
|
part 'providers.g.dart';
|
||||||
|
|
||||||
|
typedef FifoTab = (TabState, ContainerData?);
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class SelectedBangTrigger extends _$SelectedBangTrigger {
|
class SelectedBangTrigger extends _$SelectedBangTrigger {
|
||||||
// ignore: document_ignores api decision
|
// ignore: document_ignores api decision
|
||||||
@@ -111,6 +114,33 @@ EquatableValue<Map<String, TabState>> availableTabStates(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
EquatableValue<List<FifoTab>> fifoTabStates(Ref ref) {
|
||||||
|
final containerData = ref
|
||||||
|
.watch(watchContainersWithCountProvider.select((value) => value.value))
|
||||||
|
.mapNotNull(
|
||||||
|
(value) => Map.fromEntries(value.map((c) => MapEntry(c.id, c))),
|
||||||
|
);
|
||||||
|
|
||||||
|
final sortedTabs = ref.watch(
|
||||||
|
watchgetTabsFifoProvider.select((value) => value.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
final tabStates = ref.watch(tabStatesProvider);
|
||||||
|
|
||||||
|
return EquatableValue([
|
||||||
|
if (sortedTabs != null)
|
||||||
|
for (final tab in sortedTabs)
|
||||||
|
if (tabStates.containsKey(tab.id))
|
||||||
|
(
|
||||||
|
tabStates[tab.id]!,
|
||||||
|
tab.containerId.mapNotNull(
|
||||||
|
(containerId) => containerData?[containerId],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
EquatableValue<List<TabEntity>> suggestedTabEntities(
|
EquatableValue<List<TabEntity>> suggestedTabEntities(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
|
|||||||
@@ -382,6 +382,55 @@ final class AvailableTabStatesFamily extends $Family
|
|||||||
String toString() => r'availableTabStatesProvider';
|
String toString() => r'availableTabStatesProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ProviderFor(fifoTabStates)
|
||||||
|
const fifoTabStatesProvider = FifoTabStatesProvider._();
|
||||||
|
|
||||||
|
final class FifoTabStatesProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
EquatableValue<List<FifoTab>>,
|
||||||
|
EquatableValue<List<FifoTab>>,
|
||||||
|
EquatableValue<List<FifoTab>>
|
||||||
|
>
|
||||||
|
with $Provider<EquatableValue<List<FifoTab>>> {
|
||||||
|
const FifoTabStatesProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'fifoTabStatesProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$fifoTabStatesHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<EquatableValue<List<FifoTab>>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
EquatableValue<List<FifoTab>> create(Ref ref) {
|
||||||
|
return fifoTabStates(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(EquatableValue<List<FifoTab>> value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<EquatableValue<List<FifoTab>>>(
|
||||||
|
value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$fifoTabStatesHash() => r'0320c28b328d814ed358ec6a10ec770a1ed634ad';
|
||||||
|
|
||||||
@ProviderFor(suggestedTabEntities)
|
@ProviderFor(suggestedTabEntities)
|
||||||
const suggestedTabEntitiesProvider = SuggestedTabEntitiesFamily._();
|
const suggestedTabEntitiesProvider = SuggestedTabEntitiesFamily._();
|
||||||
|
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'tab_bar_dismissable.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class TabBarDismissableController extends _$TabBarDismissableController {
|
||||||
|
void show() {
|
||||||
|
state = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dismiss() {
|
||||||
|
if (!state) {
|
||||||
|
state = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-13
@@ -1,6 +1,6 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'tab_suggestions.dart';
|
part of 'tab_bar_dismissable.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
@@ -9,28 +9,29 @@ part of 'tab_suggestions.dart';
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
// ignore_for_file: type=lint, type=warning
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
@ProviderFor(TabSuggestionsController)
|
@ProviderFor(TabBarDismissableController)
|
||||||
const tabSuggestionsControllerProvider = TabSuggestionsControllerProvider._();
|
const tabBarDismissableControllerProvider =
|
||||||
|
TabBarDismissableControllerProvider._();
|
||||||
|
|
||||||
final class TabSuggestionsControllerProvider
|
final class TabBarDismissableControllerProvider
|
||||||
extends $NotifierProvider<TabSuggestionsController, bool> {
|
extends $NotifierProvider<TabBarDismissableController, bool> {
|
||||||
const TabSuggestionsControllerProvider._()
|
const TabBarDismissableControllerProvider._()
|
||||||
: super(
|
: super(
|
||||||
from: null,
|
from: null,
|
||||||
argument: null,
|
argument: null,
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'tabSuggestionsControllerProvider',
|
name: r'tabBarDismissableControllerProvider',
|
||||||
isAutoDispose: false,
|
isAutoDispose: true,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String debugGetCreateSourceHash() => _$tabSuggestionsControllerHash();
|
String debugGetCreateSourceHash() => _$tabBarDismissableControllerHash();
|
||||||
|
|
||||||
@$internal
|
@$internal
|
||||||
@override
|
@override
|
||||||
TabSuggestionsController create() => TabSuggestionsController();
|
TabBarDismissableController create() => TabBarDismissableController();
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
/// {@macro riverpod.override_with_value}
|
||||||
Override overrideWithValue(bool value) {
|
Override overrideWithValue(bool value) {
|
||||||
@@ -41,10 +42,10 @@ final class TabSuggestionsControllerProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$tabSuggestionsControllerHash() =>
|
String _$tabBarDismissableControllerHash() =>
|
||||||
r'5ce7f385b8aba14d432912cf0acb06aad04433fc';
|
r'2bb0055f5525a21982ba3d7cb6443bcad823b64f';
|
||||||
|
|
||||||
abstract class _$TabSuggestionsController extends $Notifier<bool> {
|
abstract class _$TabBarDismissableController extends $Notifier<bool> {
|
||||||
bool build();
|
bool build();
|
||||||
@$mustCallSuper
|
@$mustCallSuper
|
||||||
@override
|
@override
|
||||||
+45
-1
@@ -17,9 +17,11 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'tab_suggestions.g.dart';
|
part 'tab_view_controllers.g.dart';
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class TabSuggestionsController extends _$TabSuggestionsController {
|
class TabSuggestionsController extends _$TabSuggestionsController {
|
||||||
@@ -38,3 +40,45 @@ class TabSuggestionsController extends _$TabSuggestionsController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TabsViewMode {
|
||||||
|
grid(MdiIcons.table, 'Grid'),
|
||||||
|
tree(MdiIcons.familyTree, 'Tree');
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const TabsViewMode(this.icon, this.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class TabsViewModeController extends _$TabsViewModeController {
|
||||||
|
void set(TabsViewMode mode) {
|
||||||
|
if (mode != state) {
|
||||||
|
state = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabsViewMode build() {
|
||||||
|
return TabsViewMode.grid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class TabsReorderableController extends _$TabsReorderableController {
|
||||||
|
void toggle() {
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hide() {
|
||||||
|
if (state) {
|
||||||
|
state = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab_view_controllers.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(TabSuggestionsController)
|
||||||
|
const tabSuggestionsControllerProvider = TabSuggestionsControllerProvider._();
|
||||||
|
|
||||||
|
final class TabSuggestionsControllerProvider
|
||||||
|
extends $NotifierProvider<TabSuggestionsController, bool> {
|
||||||
|
const TabSuggestionsControllerProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'tabSuggestionsControllerProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$tabSuggestionsControllerHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
TabSuggestionsController create() => TabSuggestionsController();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(bool value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<bool>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$tabSuggestionsControllerHash() =>
|
||||||
|
r'5ce7f385b8aba14d432912cf0acb06aad04433fc';
|
||||||
|
|
||||||
|
abstract class _$TabSuggestionsController extends $Notifier<bool> {
|
||||||
|
bool build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<bool, bool>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<bool, bool>,
|
||||||
|
bool,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ProviderFor(TabsViewModeController)
|
||||||
|
const tabsViewModeControllerProvider = TabsViewModeControllerProvider._();
|
||||||
|
|
||||||
|
final class TabsViewModeControllerProvider
|
||||||
|
extends $NotifierProvider<TabsViewModeController, TabsViewMode> {
|
||||||
|
const TabsViewModeControllerProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'tabsViewModeControllerProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$tabsViewModeControllerHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
TabsViewModeController create() => TabsViewModeController();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(TabsViewMode value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<TabsViewMode>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$tabsViewModeControllerHash() =>
|
||||||
|
r'9c574458f6d5a4c6a8b5aabf96ad7f5dcd043dd0';
|
||||||
|
|
||||||
|
abstract class _$TabsViewModeController extends $Notifier<TabsViewMode> {
|
||||||
|
TabsViewMode build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<TabsViewMode, TabsViewMode>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<TabsViewMode, TabsViewMode>,
|
||||||
|
TabsViewMode,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ProviderFor(TabsReorderableController)
|
||||||
|
const tabsReorderableControllerProvider = TabsReorderableControllerProvider._();
|
||||||
|
|
||||||
|
final class TabsReorderableControllerProvider
|
||||||
|
extends $NotifierProvider<TabsReorderableController, bool> {
|
||||||
|
const TabsReorderableControllerProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'tabsReorderableControllerProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$tabsReorderableControllerHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
TabsReorderableController create() => TabsReorderableController();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(bool value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<bool>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$tabsReorderableControllerHash() =>
|
||||||
|
r'f169e9dc04055ef611f17ebe121f0f51f6a294cb';
|
||||||
|
|
||||||
|
abstract class _$TabsReorderableController extends $Notifier<bool> {
|
||||||
|
bool build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<bool, bool>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<bool, bool>,
|
||||||
|
bool,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2024-2025 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 'tree_view.g.dart';
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
class TreeViewController extends _$TreeViewController {
|
|
||||||
void toggle() {
|
|
||||||
state = !state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hide() {
|
|
||||||
if (state) {
|
|
||||||
state = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool build() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'tree_view.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// ignore_for_file: type=lint, type=warning
|
|
||||||
|
|
||||||
@ProviderFor(TreeViewController)
|
|
||||||
const treeViewControllerProvider = TreeViewControllerProvider._();
|
|
||||||
|
|
||||||
final class TreeViewControllerProvider
|
|
||||||
extends $NotifierProvider<TreeViewController, bool> {
|
|
||||||
const TreeViewControllerProvider._()
|
|
||||||
: super(
|
|
||||||
from: null,
|
|
||||||
argument: null,
|
|
||||||
retry: null,
|
|
||||||
name: r'treeViewControllerProvider',
|
|
||||||
isAutoDispose: false,
|
|
||||||
dependencies: null,
|
|
||||||
$allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String debugGetCreateSourceHash() => _$treeViewControllerHash();
|
|
||||||
|
|
||||||
@$internal
|
|
||||||
@override
|
|
||||||
TreeViewController create() => TreeViewController();
|
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
|
||||||
Override overrideWithValue(bool value) {
|
|
||||||
return $ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
providerOverride: $SyncValueProvider<bool>(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _$treeViewControllerHash() =>
|
|
||||||
r'1fac2a133d369e3bd3f8f75cdd2d0c862a6ab942';
|
|
||||||
|
|
||||||
abstract class _$TreeViewController extends $Notifier<bool> {
|
|
||||||
bool build();
|
|
||||||
@$mustCallSuper
|
|
||||||
@override
|
|
||||||
void runBuild() {
|
|
||||||
final created = build();
|
|
||||||
final ref = this.ref as $Ref<bool, bool>;
|
|
||||||
final element =
|
|
||||||
ref.element
|
|
||||||
as $ClassProviderElement<
|
|
||||||
AnyNotifier<bool, bool>,
|
|
||||||
bool,
|
|
||||||
Object?,
|
|
||||||
Object?
|
|
||||||
>;
|
|
||||||
element.handleValue(ref, created);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,23 +40,31 @@ 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/tree_view.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_bar_dismissable.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.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';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/view_tab.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/view_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/view_tabs.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/view_tabs.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
|
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart';
|
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
class _TabBar extends HookConsumerWidget {
|
class _TabBar extends HookConsumerWidget {
|
||||||
|
final bool showContextualToolbar;
|
||||||
|
final bool showQuickTabSwitcherBar;
|
||||||
final ValueNotifier<bool> showAppBar;
|
final ValueNotifier<bool> showAppBar;
|
||||||
final ValueNotifier<PersistentBottomSheetController?> sheetController;
|
final ValueNotifier<PersistentBottomSheetController?> sheetController;
|
||||||
|
|
||||||
const _TabBar({required this.showAppBar, required this.sheetController});
|
const _TabBar({
|
||||||
|
required this.showContextualToolbar,
|
||||||
|
required this.showQuickTabSwitcherBar,
|
||||||
|
required this.showAppBar,
|
||||||
|
required this.sheetController,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -76,7 +84,11 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
if (!autoHideTabBar) {
|
if (!autoHideTabBar) {
|
||||||
return Visibility(
|
return Visibility(
|
||||||
visible: !tabInFullScreen,
|
visible: !tabInFullScreen,
|
||||||
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
|
child: BrowserBottomAppBar(
|
||||||
|
displayedSheet: displayedSheet,
|
||||||
|
showContextualToolbar: showContextualToolbar,
|
||||||
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +96,10 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
final diffAcc = useRef(0.0);
|
final diffAcc = useRef(0.0);
|
||||||
|
|
||||||
void resetHiddenState() {
|
void resetHiddenState() {
|
||||||
showAppBar.value = true;
|
if (!ref.read(tabBarDismissableControllerProvider)) {
|
||||||
|
showAppBar.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
diffAcc.value = 0.0;
|
diffAcc.value = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +168,12 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
return Visibility(
|
return Visibility(
|
||||||
visible:
|
visible:
|
||||||
sheetController.value != null || (!tabInFullScreen && appBarVisible),
|
sheetController.value != null || (!tabInFullScreen && appBarVisible),
|
||||||
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
|
maintainState: true,
|
||||||
|
child: BrowserBottomAppBar(
|
||||||
|
displayedSheet: displayedSheet,
|
||||||
|
showContextualToolbar: showContextualToolbar,
|
||||||
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,8 +191,24 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final overlayController = useOverlayPortalController();
|
final overlayController = useOverlayPortalController();
|
||||||
|
|
||||||
|
final showContextualAppBar = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.tabBarShowContextualBar,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final showQuickTabSwitcherBar = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.tabBarShowQuickTabSwitcherBar,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final showAppBar = useValueNotifier(true);
|
final showAppBar = useValueNotifier(true);
|
||||||
|
|
||||||
|
ref.listen(tabBarDismissableControllerProvider, (previous, next) {
|
||||||
|
showAppBar.value = !next;
|
||||||
|
});
|
||||||
|
|
||||||
ref.listen(overlayControllerProvider, (previous, next) {
|
ref.listen(overlayControllerProvider, (previous, next) {
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
overlayController.show();
|
overlayController.show();
|
||||||
@@ -220,17 +256,25 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
//This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely
|
//This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
bottomNavigationBar: _TabBar(
|
body: Column(
|
||||||
showAppBar: showAppBar,
|
children: [
|
||||||
sheetController: sheetController,
|
_TabBar(
|
||||||
|
showAppBar: showAppBar,
|
||||||
|
sheetController: sheetController,
|
||||||
|
showContextualToolbar: showContextualAppBar,
|
||||||
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _Browser(
|
||||||
|
overlayController: overlayController,
|
||||||
|
sheetController: sheetController,
|
||||||
|
showAppBar: showAppBar,
|
||||||
|
tabInFullScreen: tabInFullScreen,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: _Browser(
|
floatingActionButton: BrowserFab(),
|
||||||
overlayController: overlayController,
|
|
||||||
sheetController: sheetController,
|
|
||||||
showAppBar: showAppBar,
|
|
||||||
tabInFullScreen: tabInFullScreen,
|
|
||||||
),
|
|
||||||
floatingActionButton: ReaderAppearanceButton(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -408,7 +452,9 @@ class _Browser extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Make sure app bar is visible
|
//Make sure app bar is visible
|
||||||
showAppBar.value = true;
|
if (!ref.read(tabBarDismissableControllerProvider)) {
|
||||||
|
showAppBar.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tabState?.isLoading == true) {
|
if (tabState?.isLoading == true) {
|
||||||
lastBackButtonPress.value = null;
|
lastBackButtonPress.value = null;
|
||||||
@@ -634,11 +680,15 @@ class _ViewTabsSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final draggableScrollableController = useDraggableScrollableController();
|
final tabsViewMode = ref.watch(tabsViewModeControllerProvider);
|
||||||
|
final tabsReorderable = ref.watch(tabsReorderableControllerProvider);
|
||||||
|
|
||||||
final treeModeEnabled = ref.watch(treeViewControllerProvider);
|
final draggableScrollableController = useDraggableScrollableController(
|
||||||
|
keys: [tabsReorderable],
|
||||||
|
);
|
||||||
|
|
||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
|
key: ValueKey(tabsReorderable),
|
||||||
controller: draggableScrollableController,
|
controller: draggableScrollableController,
|
||||||
expand: false,
|
expand: false,
|
||||||
minChildSize: 0.1,
|
minChildSize: 0.1,
|
||||||
@@ -649,26 +699,28 @@ class _ViewTabsSheet extends HookConsumerWidget {
|
|||||||
topLeft: Radius.circular(28),
|
topLeft: Radius.circular(28),
|
||||||
topRight: Radius.circular(28),
|
topRight: Radius.circular(28),
|
||||||
),
|
),
|
||||||
child: treeModeEnabled
|
child: switch (tabsViewMode) {
|
||||||
? ViewTabTreesWidget(
|
TabsViewMode.grid => ViewTabsWidget(
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
showNewTabFab: true,
|
showNewTabFab: true,
|
||||||
onClose: () {
|
tabsReorderable: tabsReorderable,
|
||||||
ref
|
draggableScrollableController: draggableScrollableController,
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
onClose: () {
|
||||||
.requestDismiss();
|
ref
|
||||||
},
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
)
|
.requestDismiss();
|
||||||
: ViewTabsWidget(
|
},
|
||||||
scrollController: scrollController,
|
),
|
||||||
showNewTabFab: true,
|
TabsViewMode.tree => ViewTabTreesWidget(
|
||||||
draggableScrollableController: draggableScrollableController,
|
scrollController: scrollController,
|
||||||
onClose: () {
|
showNewTabFab: true,
|
||||||
ref
|
onClose: () {
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
ref
|
||||||
.requestDismiss();
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
},
|
.requestDismiss();
|
||||||
),
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.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/tree_view.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/view_tabs.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/view_tabs.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
|
|
||||||
@@ -31,28 +31,32 @@ class TabViewScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final treeModeEnabled = ref.watch(treeViewControllerProvider);
|
final tabsViewMode = ref.watch(tabsViewModeControllerProvider);
|
||||||
|
final tabsReorderable = ref.watch(tabsReorderableControllerProvider);
|
||||||
|
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController(keys: [tabsReorderable]);
|
||||||
|
|
||||||
return Dialog.fullscreen(
|
return Dialog.fullscreen(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: treeModeEnabled
|
child: switch (tabsViewMode) {
|
||||||
? ViewTabTreesWidget(
|
TabsViewMode.grid => ViewTabsWidget(
|
||||||
scrollController: scrollController,
|
key: ValueKey(tabsReorderable),
|
||||||
showNewTabFab: false,
|
scrollController: scrollController,
|
||||||
onClose: () {
|
tabsReorderable: tabsReorderable,
|
||||||
const BrowserRoute().go(context);
|
showNewTabFab: false,
|
||||||
},
|
onClose: () {
|
||||||
)
|
const BrowserRoute().go(context);
|
||||||
: ViewTabsWidget(
|
},
|
||||||
scrollController: scrollController,
|
),
|
||||||
showNewTabFab: false,
|
TabsViewMode.tree => ViewTabTreesWidget(
|
||||||
onClose: () {
|
scrollController: scrollController,
|
||||||
const BrowserRoute().go(context);
|
showNewTabFab: false,
|
||||||
},
|
onClose: () {
|
||||||
),
|
const BrowserRoute().go(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|||||||
+751
-450
File diff suppressed because it is too large
Load Diff
+14
-3
@@ -22,12 +22,15 @@ 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/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/readerview/domain/providers/readerable.dart';
|
import 'package:weblibre/features/geckoview/features/readerview/domain/providers/readerable.dart';
|
||||||
|
|
||||||
class ReaderAppearanceButton extends HookConsumerWidget {
|
class BrowserFab extends HookConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final buttonVisible = ref.watch(
|
final tabBarDismissed = ref.watch(tabBarDismissableControllerProvider);
|
||||||
|
|
||||||
|
final appearanceButtonVisible = ref.watch(
|
||||||
appearanceButtonVisibilityProvider.select(
|
appearanceButtonVisibilityProvider.select(
|
||||||
(value) => value.value ?? false,
|
(value) => value.value ?? false,
|
||||||
),
|
),
|
||||||
@@ -40,7 +43,15 @@ class ReaderAppearanceButton extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return Visibility(
|
return Visibility(
|
||||||
visible: readerabilityState.active && buttonVisible,
|
visible: readerabilityState.active && appearanceButtonVisible,
|
||||||
|
replacement: tabBarDismissed
|
||||||
|
? FloatingActionButton(
|
||||||
|
child: const Icon(MdiIcons.dockBottom),
|
||||||
|
onPressed: () {
|
||||||
|
ref.read(tabBarDismissableControllerProvider.notifier).show();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ref.read(readerableServiceProvider).onAppearanceButtonTap();
|
await ref.read(readerableServiceProvider).onAppearanceButtonTap();
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
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/features/browser/presentation/dialogs/qr_code.dart';
|
||||||
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
|
class ShareMenuItemButton extends HookConsumerWidget {
|
||||||
|
const ShareMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
final String? selectedTabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuItemButton(
|
||||||
|
leadingIcon: const Icon(Icons.share),
|
||||||
|
closeOnActivate: false,
|
||||||
|
onPressed: () async {
|
||||||
|
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||||
|
|
||||||
|
await SharePlus.instance.share(ShareParams(uri: tabState.url));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
MenuController.maybeOf(context)?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Share Link'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowQrCodeMenuItemButton extends HookConsumerWidget {
|
||||||
|
const ShowQrCodeMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
final String? selectedTabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuItemButton(
|
||||||
|
leadingIcon: const Icon(Icons.qr_code),
|
||||||
|
closeOnActivate: false,
|
||||||
|
onPressed: () async {
|
||||||
|
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||||
|
|
||||||
|
await showQrCode(context, tabState.url.toString());
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
MenuController.maybeOf(context)?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Show QR Code'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareScreenshotMenuItemButton extends HookConsumerWidget {
|
||||||
|
const ShareScreenshotMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
final String? selectedTabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuItemButton(
|
||||||
|
leadingIcon: const Icon(Icons.mobile_screen_share),
|
||||||
|
closeOnActivate: false,
|
||||||
|
child: const Text('Share Screenshot'),
|
||||||
|
onPressed: () async {
|
||||||
|
final screenshot = await ref
|
||||||
|
.read(selectedTabSessionProvider)
|
||||||
|
.requestScreenshot();
|
||||||
|
|
||||||
|
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||||
|
|
||||||
|
if (screenshot != null) {
|
||||||
|
ui.decodeImageFromList(screenshot, (result) async {
|
||||||
|
final png = await result.toByteData(format: ui.ImageByteFormat.png);
|
||||||
|
|
||||||
|
if (png != null) {
|
||||||
|
final file = XFile.fromData(
|
||||||
|
png.buffer.asUint8List(),
|
||||||
|
mimeType: 'image/png',
|
||||||
|
);
|
||||||
|
|
||||||
|
await SharePlus.instance.share(
|
||||||
|
ShareParams(files: [file], subject: tabState.titleOrAuthority),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
MenuController.maybeOf(context)?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LaunchExternalMenuItemButton extends HookConsumerWidget {
|
||||||
|
const LaunchExternalMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
final String? selectedTabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuItemButton(
|
||||||
|
leadingIcon: const Icon(Icons.open_in_browser),
|
||||||
|
closeOnActivate: false,
|
||||||
|
child: const Text('Launch External'),
|
||||||
|
onPressed: () async {
|
||||||
|
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||||
|
|
||||||
|
await ui_helper.launchUrlFeedback(context, tabState.url);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
MenuController.maybeOf(context)?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CopyAddressMenuItemButton extends HookConsumerWidget {
|
||||||
|
const CopyAddressMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
final String? selectedTabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuItemButton(
|
||||||
|
leadingIcon: const Icon(MdiIcons.contentCopy),
|
||||||
|
closeOnActivate: false,
|
||||||
|
child: const Text('Copy Address'),
|
||||||
|
onPressed: () async {
|
||||||
|
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||||
|
|
||||||
|
await Clipboard.setData(ClipboardData(text: tabState.url.toString()));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
MenuController.maybeOf(context)?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
-2
@@ -26,13 +26,11 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
|||||||
class TabCreationMenu extends HookConsumerWidget {
|
class TabCreationMenu extends HookConsumerWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final MenuController controller;
|
final MenuController controller;
|
||||||
final String? selectedTabId;
|
|
||||||
|
|
||||||
const TabCreationMenu({
|
const TabCreationMenu({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.selectedTabId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -18,23 +18,19 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.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:flutter_mozilla_components/flutter_mozilla_components.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
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/providers/desktop_mode.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/desktop_mode.dart';
|
||||||
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/presentation/dialogs/qr_code.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.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/readerview/presentation/widgets/reader_button.dart';
|
||||||
@@ -315,75 +311,11 @@ class TabMenu extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
SubmenuButton(
|
SubmenuButton(
|
||||||
menuChildren: [
|
menuChildren: [
|
||||||
MenuItemButton(
|
CopyAddressMenuItemButton(selectedTabId: selectedTabId),
|
||||||
leadingIcon: const Icon(MdiIcons.contentCopy),
|
LaunchExternalMenuItemButton(selectedTabId: selectedTabId),
|
||||||
child: const Text('Copy Address'),
|
ShareScreenshotMenuItemButton(selectedTabId: selectedTabId),
|
||||||
onPressed: () async {
|
ShareMenuItemButton(selectedTabId: selectedTabId),
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
|
||||||
|
|
||||||
await Clipboard.setData(
|
|
||||||
ClipboardData(text: tabState.url.toString()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
leadingIcon: const Icon(Icons.open_in_browser),
|
|
||||||
child: const Text('Launch External'),
|
|
||||||
onPressed: () async {
|
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
|
||||||
|
|
||||||
await ui_helper.launchUrlFeedback(context, tabState.url);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
leadingIcon: const Icon(Icons.mobile_screen_share),
|
|
||||||
child: const Text('Share Screenshot'),
|
|
||||||
onPressed: () async {
|
|
||||||
final screenshot = await ref
|
|
||||||
.read(selectedTabSessionProvider)
|
|
||||||
.requestScreenshot();
|
|
||||||
|
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
|
||||||
|
|
||||||
if (screenshot != null) {
|
|
||||||
ui.decodeImageFromList(screenshot, (result) async {
|
|
||||||
final png = await result.toByteData(
|
|
||||||
format: ui.ImageByteFormat.png,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (png != null) {
|
|
||||||
final file = XFile.fromData(
|
|
||||||
png.buffer.asUint8List(),
|
|
||||||
mimeType: 'image/png',
|
|
||||||
);
|
|
||||||
|
|
||||||
await SharePlus.instance.share(
|
|
||||||
ShareParams(files: [file], subject: tabState.title),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
leadingIcon: const Icon(Icons.share),
|
|
||||||
onPressed: () async {
|
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
|
||||||
|
|
||||||
await SharePlus.instance.share(ShareParams(uri: tabState.url));
|
|
||||||
},
|
|
||||||
child: const Text('Share Link'),
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
leadingIcon: const Icon(Icons.qr_code),
|
|
||||||
onPressed: () async {
|
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
|
||||||
|
|
||||||
await showQrCode(context, tabState.url.toString());
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
child: const Text('Show QR Code'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
leadingIcon: const Icon(Icons.share),
|
leadingIcon: const Icon(Icons.share),
|
||||||
child: const Text('Share'),
|
child: const Text('Share'),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import 'package:fast_equatable/fast_equatable.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.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:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
|
import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
@@ -36,8 +37,7 @@ 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/providers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_suggestions.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tree_view.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
|
||||||
@@ -50,6 +50,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c
|
|||||||
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
||||||
|
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||||
import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
|
import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ int calculateCrossAxisItemCount({
|
|||||||
return crossAxisCount;
|
return crossAxisCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
double calculateItemHeight({
|
Size calculateItemSize({
|
||||||
required double screenWidth,
|
required double screenWidth,
|
||||||
required double childAspectRatio,
|
required double childAspectRatio,
|
||||||
required double horizontalPadding,
|
required double horizontalPadding,
|
||||||
@@ -81,9 +82,8 @@ double calculateItemHeight({
|
|||||||
screenWidth - totalHorizontalPadding - totalCrossAxisSpacing;
|
screenWidth - totalHorizontalPadding - totalCrossAxisSpacing;
|
||||||
final itemWidth = availableWidth / crossAxisCount;
|
final itemWidth = availableWidth / crossAxisCount;
|
||||||
final itemHeight = itemWidth / childAspectRatio;
|
final itemHeight = itemWidth / childAspectRatio;
|
||||||
final totalItemHeight = itemHeight + mainAxisSpacing;
|
|
||||||
|
|
||||||
return totalItemHeight;
|
return Size(itemWidth, itemHeight + mainAxisSpacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TabDraggable extends HookConsumerWidget {
|
class _TabDraggable extends HookConsumerWidget {
|
||||||
@@ -165,10 +165,10 @@ class _TabDraggable extends HookConsumerWidget {
|
|||||||
class _TabViewHeader extends HookConsumerWidget {
|
class _TabViewHeader extends HookConsumerWidget {
|
||||||
static const headerSize = 124.0;
|
static const headerSize = 124.0;
|
||||||
|
|
||||||
final bool treeViewEnabled;
|
final TabsViewMode tabsViewMode;
|
||||||
final VoidCallback onClose;
|
final VoidCallback onClose;
|
||||||
|
|
||||||
const _TabViewHeader({required this.onClose, required this.treeViewEnabled});
|
const _TabViewHeader({required this.onClose, required this.tabsViewMode});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -176,6 +176,9 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
final searchTextFocus = useFocusNode();
|
final searchTextFocus = useFocusNode();
|
||||||
final searchTextController = useTextEditingController();
|
final searchTextController = useTextEditingController();
|
||||||
|
|
||||||
|
final viewModeMenuController = useMenuController();
|
||||||
|
final tabsActionMenuController = useMenuController();
|
||||||
|
|
||||||
final hasSearchText = useListenableSelector(
|
final hasSearchText = useListenableSelector(
|
||||||
searchTextController,
|
searchTextController,
|
||||||
() => searchTextController.text.isNotEmpty,
|
() => searchTextController.text.isNotEmpty,
|
||||||
@@ -218,8 +221,8 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref
|
ref
|
||||||
.read(treeViewControllerProvider.notifier)
|
.read(tabsViewModeControllerProvider.notifier)
|
||||||
.hide();
|
.set(TabsViewMode.grid);
|
||||||
|
|
||||||
searchMode.value = true;
|
searchMode.value = true;
|
||||||
searchTextFocus.requestFocus();
|
searchTextFocus.requestFocus();
|
||||||
@@ -229,17 +232,40 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
height: 32,
|
height: 32,
|
||||||
child: VerticalDivider(indent: 4, endIndent: 4),
|
child: VerticalDivider(indent: 4, endIndent: 4),
|
||||||
),
|
),
|
||||||
IconButton(
|
MenuAnchor(
|
||||||
icon: const Icon(MdiIcons.familyTree),
|
controller: viewModeMenuController,
|
||||||
selectedIcon: const Icon(MdiIcons.table),
|
menuChildren: TabsViewMode.values
|
||||||
isSelected: treeViewEnabled,
|
.map(
|
||||||
iconSize: 18,
|
(mode) => MenuItemButton(
|
||||||
padding: EdgeInsets.zero,
|
leadingIcon: Icon(mode.icon),
|
||||||
onPressed: () {
|
child: Text(mode.label),
|
||||||
ref
|
onPressed: () {
|
||||||
.read(treeViewControllerProvider.notifier)
|
ref
|
||||||
.toggle();
|
.read(
|
||||||
},
|
tabsViewModeControllerProvider
|
||||||
|
.notifier,
|
||||||
|
)
|
||||||
|
.set(mode);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (viewModeMenuController.isOpen) {
|
||||||
|
viewModeMenuController.close();
|
||||||
|
} else {
|
||||||
|
viewModeMenuController.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(tabsViewMode.icon, size: 18),
|
||||||
|
const Icon(Icons.arrow_drop_down, size: 18),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (enableAiFeatures)
|
if (enableAiFeatures)
|
||||||
Consumer(
|
Consumer(
|
||||||
@@ -264,26 +290,122 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final tabsReorderabe = ref.watch(
|
||||||
|
tabsReorderableControllerProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
return IconButton.filledTonal(
|
||||||
|
icon: const Icon(MdiIcons.orderNumericAscending),
|
||||||
|
isSelected: tabsReorderabe,
|
||||||
|
iconSize: 18,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
tabsReorderableControllerProvider
|
||||||
|
.notifier,
|
||||||
|
)
|
||||||
|
.toggle();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
MenuAnchor(
|
||||||
onPressed: () async {
|
controller: tabsActionMenuController,
|
||||||
final container = ref.read(selectedContainerProvider);
|
menuChildren: [
|
||||||
|
MenuItemButton(
|
||||||
|
leadingIcon: const Icon(MdiIcons.bookmarkPlusOutline),
|
||||||
|
child: const Text('Bookmark all'),
|
||||||
|
onPressed: () async {
|
||||||
|
final containerId = ref.read(
|
||||||
|
selectedContainerProvider,
|
||||||
|
);
|
||||||
|
|
||||||
final count = await ref
|
final tabData = await ref
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
.closeAllTabsByContainer(container);
|
.getContainerTabsData(containerId);
|
||||||
|
|
||||||
if (context.mounted) {
|
for (final tab in tabData) {
|
||||||
ui_helper.showTabUndoClose(
|
if (context.mounted) {
|
||||||
context,
|
await BookmarkEntryAddRoute(
|
||||||
ref.read(tabRepositoryProvider.notifier).undoClose,
|
bookmarkInfo: jsonEncode(
|
||||||
count: count,
|
BookmarkInfo(
|
||||||
);
|
title: tab.title,
|
||||||
}
|
url: tab.url.toString(),
|
||||||
},
|
).encode(),
|
||||||
icon: const Icon(MdiIcons.closeBoxMultiple),
|
),
|
||||||
label: const Text('Close All'),
|
).push(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
leadingIcon: const Icon(MdiIcons.closeBoxMultiple),
|
||||||
|
child: const Text('Close All'),
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await showDialog<bool?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
icon: const Icon(Icons.warning),
|
||||||
|
title: const Text('Close All Tabs'),
|
||||||
|
content: const Text(
|
||||||
|
'Are you sure you want to close all displayed tabs?',
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
final container = ref.read(
|
||||||
|
selectedContainerProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
final count = await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.closeAllTabsByContainer(container);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showTabUndoClose(
|
||||||
|
context,
|
||||||
|
ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.undoClose,
|
||||||
|
count: count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (tabsActionMenuController.isOpen) {
|
||||||
|
tabsActionMenuController.close();
|
||||||
|
} else {
|
||||||
|
tabsActionMenuController.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(MdiIcons.dotsVertical),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -318,7 +440,7 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!treeViewEnabled)
|
if (tabsViewMode == TabsViewMode.grid)
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
final selectedContainer = ref.watch(
|
final selectedContainer = ref.watch(
|
||||||
@@ -372,9 +494,14 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
|
|
||||||
class _TabView extends HookConsumerWidget {
|
class _TabView extends HookConsumerWidget {
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
final bool tabsReorderable;
|
||||||
final VoidCallback onClose;
|
final VoidCallback onClose;
|
||||||
|
|
||||||
const _TabView({required this.scrollController, required this.onClose});
|
const _TabView({
|
||||||
|
required this.scrollController,
|
||||||
|
required this.tabsReorderable,
|
||||||
|
required this.onClose,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -415,8 +542,8 @@ class _TabView extends HookConsumerWidget {
|
|||||||
return math.max(math.min(calculatedCount, itemCount), 2);
|
return math.max(math.min(calculatedCount, itemCount), 2);
|
||||||
}, [screenWidth, itemCount]);
|
}, [screenWidth, itemCount]);
|
||||||
|
|
||||||
final itemHeight = useMemoized(
|
final itemSize = useMemoized(
|
||||||
() => calculateItemHeight(
|
() => calculateItemSize(
|
||||||
screenWidth: screenWidth,
|
screenWidth: screenWidth,
|
||||||
childAspectRatio: 0.75,
|
childAspectRatio: 0.75,
|
||||||
horizontalPadding: 4.0,
|
horizontalPadding: 4.0,
|
||||||
@@ -438,7 +565,7 @@ class _TabView extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
final offset = (index ~/ 2) * itemHeight;
|
final offset = (index ~/ 2) * itemSize.height;
|
||||||
|
|
||||||
if (offset != scrollController.offset) {
|
if (offset != scrollController.offset) {
|
||||||
lastScroll.value = activeTab;
|
lastScroll.value = activeTab;
|
||||||
@@ -465,117 +592,192 @@ class _TabView extends HookConsumerWidget {
|
|||||||
fadingSize: 5,
|
fadingSize: 5,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
builder: (context, controller) {
|
builder: (context, controller) {
|
||||||
return ReorderableBuilder.builder(
|
return !tabsReorderable
|
||||||
//Rebuild when cross axis count changes
|
? _TabGrid(
|
||||||
key: ValueKey(crossAxisCount),
|
key: ValueKey(crossAxisCount),
|
||||||
scrollController: controller,
|
|
||||||
itemCount: itemCount,
|
|
||||||
onDragStarted: (index) {
|
|
||||||
ref.read(willAcceptDropProvider.notifier).clear();
|
|
||||||
},
|
|
||||||
onReorderPositions: (positions) async {
|
|
||||||
assert(positions.length == 1, 'Not ready for multiple reorders');
|
|
||||||
|
|
||||||
final oldIndex = positions.first.oldIndex;
|
|
||||||
final newIndex = positions.first.newIndex;
|
|
||||||
|
|
||||||
final containerRepository = ref.read(
|
|
||||||
containerRepositoryProvider.notifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
//Suggestions are at the end and not reorderable, so skip
|
|
||||||
if (oldIndex >= filteredTabEntities.value.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tabId = filteredTabEntities.value[oldIndex].tabId;
|
|
||||||
final containerId = await ref
|
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
|
||||||
.getContainerTabId(tabId);
|
|
||||||
|
|
||||||
final String key;
|
|
||||||
if (newIndex <= 0) {
|
|
||||||
key = await containerRepository.getLeadingOrderKey(containerId);
|
|
||||||
} else if (newIndex >= filteredTabEntities.value.length - 1) {
|
|
||||||
key = await containerRepository.getTrailingOrderKey(
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (newIndex < oldIndex) {
|
|
||||||
key = (await containerRepository.getOrderKeyAfterTab(
|
|
||||||
filteredTabEntities.value[newIndex - 1].tabId,
|
|
||||||
containerId,
|
|
||||||
))!;
|
|
||||||
} else {
|
|
||||||
key = await containerRepository.getOrderKeyBeforeTab(
|
|
||||||
filteredTabEntities.value[newIndex + 1].tabId,
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ref
|
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
|
||||||
.assignOrderKey(tabId, key);
|
|
||||||
},
|
|
||||||
childBuilder: (itemBuilder) {
|
|
||||||
return GridView.builder(
|
|
||||||
controller: controller,
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
//Sync values for itemHeight calculation _calculateItemHeight
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
mainAxisSpacing: 8.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
),
|
itemCount: itemCount,
|
||||||
itemCount: itemCount,
|
scrollController: controller,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (widget, _) {
|
||||||
final Widget tab;
|
if (widget is CustomDraggable) {
|
||||||
if (index < filteredTabEntities.value.length) {
|
return LongPressDraggable(
|
||||||
final entity = filteredTabEntities.value[index];
|
feedback: Material(
|
||||||
tab = CustomDraggable(
|
color: Colors
|
||||||
key: Key(entity.tabId),
|
.transparent, // removes white corners when having shadow
|
||||||
data: TabDragData(entity.tabId),
|
child: Transform.scale(
|
||||||
child: _TabDraggable(entity: entity, onClose: onClose),
|
scale: 1.05,
|
||||||
);
|
child: SizedBox(
|
||||||
} else {
|
height: itemSize.height,
|
||||||
final suggestedIndex =
|
width: itemSize.width,
|
||||||
index - filteredTabEntities.value.length;
|
child: widget.child,
|
||||||
final entity = suggestedTabEntities.value[suggestedIndex];
|
),
|
||||||
|
),
|
||||||
tab = CustomDraggable(
|
|
||||||
key: Key('suggested_${entity.tabId}'),
|
|
||||||
child: _TabDraggable(
|
|
||||||
entity: entity,
|
|
||||||
onClose: onClose,
|
|
||||||
suggestedContainerId: ref.watch(
|
|
||||||
selectedContainerProvider,
|
|
||||||
),
|
),
|
||||||
),
|
data: widget.data,
|
||||||
);
|
child: widget.child,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return itemBuilder(tab, index);
|
return widget;
|
||||||
},
|
},
|
||||||
);
|
suggestedContainerId: containerId,
|
||||||
},
|
filteredTabEntities: filteredTabEntities,
|
||||||
);
|
suggestedTabEntities: suggestedTabEntities,
|
||||||
|
onClose: onClose,
|
||||||
|
)
|
||||||
|
: ReorderableBuilder.builder(
|
||||||
|
//Rebuild when cross axis count changes
|
||||||
|
key: ValueKey(crossAxisCount),
|
||||||
|
scrollController: controller,
|
||||||
|
itemCount: itemCount,
|
||||||
|
onDragStarted: (index) {
|
||||||
|
ref.read(willAcceptDropProvider.notifier).clear();
|
||||||
|
},
|
||||||
|
onReorderPositions: (positions) async {
|
||||||
|
assert(
|
||||||
|
positions.length == 1,
|
||||||
|
'Not ready for multiple reorders',
|
||||||
|
);
|
||||||
|
|
||||||
|
final oldIndex = positions.first.oldIndex;
|
||||||
|
final newIndex = positions.first.newIndex;
|
||||||
|
|
||||||
|
final containerRepository = ref.read(
|
||||||
|
containerRepositoryProvider.notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
//Suggestions are at the end and not reorderable, so skip
|
||||||
|
if (oldIndex >= filteredTabEntities.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tabId = filteredTabEntities.value[oldIndex].tabId;
|
||||||
|
final containerId = await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.getContainerTabId(tabId);
|
||||||
|
|
||||||
|
final String key;
|
||||||
|
if (newIndex <= 0) {
|
||||||
|
key = await containerRepository.getLeadingOrderKey(
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
} else if (newIndex >=
|
||||||
|
filteredTabEntities.value.length - 1) {
|
||||||
|
key = await containerRepository.getTrailingOrderKey(
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (newIndex < oldIndex) {
|
||||||
|
key = (await containerRepository.getOrderKeyAfterTab(
|
||||||
|
filteredTabEntities.value[newIndex - 1].tabId,
|
||||||
|
containerId,
|
||||||
|
))!;
|
||||||
|
} else {
|
||||||
|
key = await containerRepository.getOrderKeyBeforeTab(
|
||||||
|
filteredTabEntities.value[newIndex + 1].tabId,
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.assignOrderKey(tabId, key);
|
||||||
|
},
|
||||||
|
childBuilder: (itemBuilder) {
|
||||||
|
return _TabGrid(
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
itemCount: itemCount,
|
||||||
|
scrollController: controller,
|
||||||
|
itemBuilder: itemBuilder,
|
||||||
|
suggestedContainerId: containerId,
|
||||||
|
filteredTabEntities: filteredTabEntities,
|
||||||
|
suggestedTabEntities: suggestedTabEntities,
|
||||||
|
onClose: onClose,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TabGrid extends StatelessWidget {
|
||||||
|
const _TabGrid({
|
||||||
|
super.key,
|
||||||
|
required this.crossAxisCount,
|
||||||
|
required this.scrollController,
|
||||||
|
required this.itemCount,
|
||||||
|
required this.itemBuilder,
|
||||||
|
required this.suggestedContainerId,
|
||||||
|
required this.filteredTabEntities,
|
||||||
|
required this.suggestedTabEntities,
|
||||||
|
required this.onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int crossAxisCount;
|
||||||
|
final int itemCount;
|
||||||
|
final ScrollController? scrollController;
|
||||||
|
final String? suggestedContainerId;
|
||||||
|
final EquatableValue<List<TabEntity>> filteredTabEntities;
|
||||||
|
final EquatableValue<List<TabEntity>> suggestedTabEntities;
|
||||||
|
final Widget Function(Widget, int)? itemBuilder;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GridView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
//Sync values for itemHeight calculation _calculateItemHeight
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
mainAxisSpacing: 8.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
),
|
||||||
|
itemCount: itemCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final Widget tab;
|
||||||
|
if (index < filteredTabEntities.value.length) {
|
||||||
|
final entity = filteredTabEntities.value[index];
|
||||||
|
tab = CustomDraggable(
|
||||||
|
key: Key(entity.tabId),
|
||||||
|
data: TabDragData(entity.tabId),
|
||||||
|
child: _TabDraggable(entity: entity, onClose: onClose),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final suggestedIndex = index - filteredTabEntities.value.length;
|
||||||
|
final entity = suggestedTabEntities.value[suggestedIndex];
|
||||||
|
|
||||||
|
tab = CustomDraggable(
|
||||||
|
key: Key('suggested_${entity.tabId}'),
|
||||||
|
child: _TabDraggable(
|
||||||
|
entity: entity,
|
||||||
|
onClose: onClose,
|
||||||
|
suggestedContainerId: suggestedContainerId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (itemBuilder != null) ? itemBuilder!(tab, index) : tab;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ViewTabsWidget extends HookConsumerWidget {
|
class ViewTabsWidget extends HookConsumerWidget {
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final DraggableScrollableController? draggableScrollableController;
|
final DraggableScrollableController? draggableScrollableController;
|
||||||
final VoidCallback onClose;
|
|
||||||
final bool showNewTabFab;
|
final bool showNewTabFab;
|
||||||
|
final bool tabsReorderable;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
|
||||||
const ViewTabsWidget({
|
const ViewTabsWidget({
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
this.draggableScrollableController,
|
this.draggableScrollableController,
|
||||||
|
required this.tabsReorderable,
|
||||||
required this.showNewTabFab,
|
required this.showNewTabFab,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
@@ -596,14 +798,21 @@ class ViewTabsWidget extends HookConsumerWidget {
|
|||||||
controller: draggableScrollableController,
|
controller: draggableScrollableController,
|
||||||
child: _TabViewHeader(
|
child: _TabViewHeader(
|
||||||
onClose: onClose,
|
onClose: onClose,
|
||||||
treeViewEnabled: false,
|
tabsViewMode: TabsViewMode.grid,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
) ??
|
) ??
|
||||||
_TabViewHeader(onClose: onClose, treeViewEnabled: false),
|
_TabViewHeader(
|
||||||
|
onClose: onClose,
|
||||||
|
tabsViewMode: TabsViewMode.grid,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
body: _TabView(scrollController: scrollController, onClose: onClose),
|
body: _TabView(
|
||||||
|
scrollController: scrollController,
|
||||||
|
tabsReorderable: tabsReorderable,
|
||||||
|
onClose: onClose,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (showNewTabFab)
|
if (showNewTabFab)
|
||||||
Padding(
|
Padding(
|
||||||
@@ -650,7 +859,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
_TabViewHeader(onClose: onClose, treeViewEnabled: true),
|
_TabViewHeader(onClose: onClose, tabsViewMode: TabsViewMode.tree),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: HookConsumer(
|
child: HookConsumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
@@ -684,8 +893,8 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}, [screenWidth, filteredTabEntities.value.length]);
|
}, [screenWidth, filteredTabEntities.value.length]);
|
||||||
|
|
||||||
final itemHeight = useMemoized(
|
final itemSize = useMemoized(
|
||||||
() => calculateItemHeight(
|
() => calculateItemSize(
|
||||||
screenWidth: screenWidth,
|
screenWidth: screenWidth,
|
||||||
childAspectRatio: 0.75,
|
childAspectRatio: 0.75,
|
||||||
horizontalPadding: 4.0,
|
horizontalPadding: 4.0,
|
||||||
@@ -702,7 +911,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
final offset = (index ~/ 2) * itemHeight;
|
final offset = (index ~/ 2) * itemSize.height;
|
||||||
|
|
||||||
if (offset != scrollController.offset) {
|
if (offset != scrollController.offset) {
|
||||||
unawaited(
|
unawaited(
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
|||||||
return query.map((row) => row.read(db.tab.id)!);
|
return query.map((row) => row.read(db.tab.id)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Selectable<TabData> getTabsFifo({int limit = 25}) {
|
||||||
|
return select(db.tab)
|
||||||
|
..limit(limit)
|
||||||
|
..orderBy([(t) => OrderingTerm.desc(t.timestamp)]);
|
||||||
|
}
|
||||||
|
|
||||||
SingleOrNullSelectable<String?> getTabContainerId(String tabId) {
|
SingleOrNullSelectable<String?> getTabContainerId(String tabId) {
|
||||||
final query = selectOnly(db.tab)
|
final query = selectOnly(db.tab)
|
||||||
..addColumns([db.tab.containerId])
|
..addColumns([db.tab.containerId])
|
||||||
|
|||||||
@@ -74,6 +74,13 @@ Stream<List<String>> watchContainerTabIds(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
Stream<List<TabData>> watchgetTabsFifo(Ref ref) {
|
||||||
|
final db = ref.watch(tabDatabaseProvider);
|
||||||
|
|
||||||
|
return db.tabDao.getTabsFifo().watch();
|
||||||
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
Future<int> containerTabCount(Ref ref, ContainerFilter containerFilter) {
|
Future<int> containerTabCount(Ref ref, ContainerFilter containerFilter) {
|
||||||
return ref.watch(
|
return ref.watch(
|
||||||
|
|||||||
@@ -221,6 +221,45 @@ final class WatchContainerTabIdsFamily extends $Family
|
|||||||
String toString() => r'watchContainerTabIdsProvider';
|
String toString() => r'watchContainerTabIdsProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ProviderFor(watchgetTabsFifo)
|
||||||
|
const watchgetTabsFifoProvider = WatchgetTabsFifoProvider._();
|
||||||
|
|
||||||
|
final class WatchgetTabsFifoProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<List<TabData>>,
|
||||||
|
List<TabData>,
|
||||||
|
Stream<List<TabData>>
|
||||||
|
>
|
||||||
|
with $FutureModifier<List<TabData>>, $StreamProvider<List<TabData>> {
|
||||||
|
const WatchgetTabsFifoProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'watchgetTabsFifoProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$watchgetTabsFifoHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$StreamProviderElement<List<TabData>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $StreamProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<TabData>> create(Ref ref) {
|
||||||
|
return watchgetTabsFifo(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$watchgetTabsFifoHash() => r'b0f58ca1688003ba46a55de1ca29d8253fc3de3a';
|
||||||
|
|
||||||
@ProviderFor(containerTabCount)
|
@ProviderFor(containerTabCount)
|
||||||
const containerTabCountProvider = ContainerTabCountFamily._();
|
const containerTabCountProvider = ContainerTabCountFamily._();
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.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/tabs/data/database/definitions.drift.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||||
@@ -87,11 +88,7 @@ class TabDataRepository extends _$TabDataRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<int> closeAllTabsByHost(String? containerId, String host) async {
|
Future<int> closeAllTabsByHost(String? containerId, String host) async {
|
||||||
final tabs = await ref
|
final tabs = await getContainerTabsData(containerId);
|
||||||
.read(tabDatabaseProvider)
|
|
||||||
.containerDao
|
|
||||||
.getContainerTabsData(containerId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
final filtered = tabs
|
final filtered = tabs
|
||||||
.where((tab) => tab.url?.host == host)
|
.where((tab) => tab.url?.host == host)
|
||||||
@@ -105,6 +102,14 @@ class TabDataRepository extends _$TabDataRepository {
|
|||||||
return filtered.length;
|
return filtered.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<TabData>> getContainerTabsData(String? containerId) {
|
||||||
|
return ref
|
||||||
|
.read(tabDatabaseProvider)
|
||||||
|
.containerDao
|
||||||
|
.getContainerTabsData(containerId)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> getContainerTabId(String tabId) {
|
Future<String?> getContainerTabId(String tabId) {
|
||||||
return ref
|
return ref
|
||||||
.read(tabDatabaseProvider)
|
.read(tabDatabaseProvider)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class TabDataRepositoryProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$tabDataRepositoryHash() => r'8f8a1dff5e5be5cbe59562c86b2e57c229b97f8a';
|
String _$tabDataRepositoryHash() => r'e12750bc1962878a5bf5140d97521d642001994f';
|
||||||
|
|
||||||
abstract class _$TabDataRepository extends $Notifier<void> {
|
abstract class _$TabDataRepository extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
@@ -362,6 +362,38 @@ class GeneralSettingsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
title: const Text('Show Contextual Tab Bar'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'Show additional bottom toolbar for navigation and actions',
|
||||||
|
),
|
||||||
|
secondary: const Icon(MdiIcons.dockBottom),
|
||||||
|
value: generalSettings.tabBarShowContextualBar,
|
||||||
|
onChanged: (value) async {
|
||||||
|
await ref
|
||||||
|
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||||
|
.save(
|
||||||
|
(currentSettings) => currentSettings.copyWith
|
||||||
|
.tabBarShowContextualBar(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
title: const Text('Show Quick Tab Switcher Bar'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'Show additional toolbar to quickly switch to recently used tabs',
|
||||||
|
),
|
||||||
|
secondary: const Icon(MdiIcons.dockBottom),
|
||||||
|
value: generalSettings.tabBarShowQuickTabSwitcherBar,
|
||||||
|
onChanged: (value) async {
|
||||||
|
await ref
|
||||||
|
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||||
|
.save(
|
||||||
|
(currentSettings) => currentSettings.copyWith
|
||||||
|
.tabBarShowQuickTabSwitcherBar(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
SwitchListTile.adaptive(
|
SwitchListTile.adaptive(
|
||||||
title: const Text('Create Child Tabs'),
|
title: const Text('Create Child Tabs'),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class GeneralSettings with FastEquatable {
|
|||||||
final Duration historyAutoCleanInterval;
|
final Duration historyAutoCleanInterval;
|
||||||
final bool tabViewBottomSheet;
|
final bool tabViewBottomSheet;
|
||||||
final bool tabBarReaderView;
|
final bool tabBarReaderView;
|
||||||
|
final bool tabBarShowContextualBar;
|
||||||
|
final bool tabBarShowQuickTabSwitcherBar;
|
||||||
|
|
||||||
GeneralSettings({
|
GeneralSettings({
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
@@ -90,6 +92,8 @@ class GeneralSettings with FastEquatable {
|
|||||||
required this.historyAutoCleanInterval,
|
required this.historyAutoCleanInterval,
|
||||||
required this.tabViewBottomSheet,
|
required this.tabViewBottomSheet,
|
||||||
required this.tabBarReaderView,
|
required this.tabBarReaderView,
|
||||||
|
required this.tabBarShowContextualBar,
|
||||||
|
required this.tabBarShowQuickTabSwitcherBar,
|
||||||
});
|
});
|
||||||
|
|
||||||
GeneralSettings.withDefaults({
|
GeneralSettings.withDefaults({
|
||||||
@@ -109,6 +113,8 @@ class GeneralSettings with FastEquatable {
|
|||||||
Duration? historyAutoCleanInterval,
|
Duration? historyAutoCleanInterval,
|
||||||
bool? tabViewBottomSheet,
|
bool? tabViewBottomSheet,
|
||||||
bool? tabBarReaderView,
|
bool? tabBarReaderView,
|
||||||
|
bool? tabBarShowContextualBar,
|
||||||
|
bool? tabBarShowQuickTabSwitcherBar,
|
||||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||||
enableReadability = enableReadability ?? true,
|
enableReadability = enableReadability ?? true,
|
||||||
enforceReadability = enforceReadability ?? false,
|
enforceReadability = enforceReadability ?? false,
|
||||||
@@ -126,7 +132,9 @@ class GeneralSettings with FastEquatable {
|
|||||||
historyAutoCleanInterval =
|
historyAutoCleanInterval =
|
||||||
historyAutoCleanInterval ?? const Duration(days: 90),
|
historyAutoCleanInterval ?? const Duration(days: 90),
|
||||||
tabViewBottomSheet = tabViewBottomSheet ?? false,
|
tabViewBottomSheet = tabViewBottomSheet ?? false,
|
||||||
tabBarReaderView = tabBarReaderView ?? false;
|
tabBarReaderView = tabBarReaderView ?? false,
|
||||||
|
tabBarShowContextualBar = tabBarShowContextualBar ?? true,
|
||||||
|
tabBarShowQuickTabSwitcherBar = tabBarShowQuickTabSwitcherBar ?? true;
|
||||||
|
|
||||||
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
|
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
_$GeneralSettingsFromJson(json);
|
_$GeneralSettingsFromJson(json);
|
||||||
@@ -151,5 +159,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
historyAutoCleanInterval,
|
historyAutoCleanInterval,
|
||||||
tabViewBottomSheet,
|
tabViewBottomSheet,
|
||||||
tabBarReaderView,
|
tabBarReaderView,
|
||||||
|
tabBarShowContextualBar,
|
||||||
|
tabBarShowQuickTabSwitcherBar,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
|
|
||||||
GeneralSettings tabBarReaderView(bool tabBarReaderView);
|
GeneralSettings tabBarReaderView(bool tabBarReaderView);
|
||||||
|
|
||||||
|
GeneralSettings tabBarShowContextualBar(bool tabBarShowContextualBar);
|
||||||
|
|
||||||
|
GeneralSettings tabBarShowQuickTabSwitcherBar(
|
||||||
|
bool tabBarShowQuickTabSwitcherBar,
|
||||||
|
);
|
||||||
|
|
||||||
/// Creates a new instance with the provided field values.
|
/// 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)`.
|
/// 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)`.
|
||||||
///
|
///
|
||||||
@@ -69,6 +75,8 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
Duration historyAutoCleanInterval,
|
Duration historyAutoCleanInterval,
|
||||||
bool tabViewBottomSheet,
|
bool tabViewBottomSheet,
|
||||||
bool tabBarReaderView,
|
bool tabBarReaderView,
|
||||||
|
bool tabBarShowContextualBar,
|
||||||
|
bool tabBarShowQuickTabSwitcherBar,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +153,15 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
GeneralSettings tabBarReaderView(bool tabBarReaderView) =>
|
GeneralSettings tabBarReaderView(bool tabBarReaderView) =>
|
||||||
call(tabBarReaderView: tabBarReaderView);
|
call(tabBarReaderView: tabBarReaderView);
|
||||||
|
|
||||||
|
@override
|
||||||
|
GeneralSettings tabBarShowContextualBar(bool tabBarShowContextualBar) =>
|
||||||
|
call(tabBarShowContextualBar: tabBarShowContextualBar);
|
||||||
|
|
||||||
|
@override
|
||||||
|
GeneralSettings tabBarShowQuickTabSwitcherBar(
|
||||||
|
bool tabBarShowQuickTabSwitcherBar,
|
||||||
|
) => call(tabBarShowQuickTabSwitcherBar: tabBarShowQuickTabSwitcherBar);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
/// Creates a new instance with the provided field values.
|
/// 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)`.
|
/// 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)`.
|
||||||
@@ -170,6 +187,8 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
Object? historyAutoCleanInterval = const $CopyWithPlaceholder(),
|
Object? historyAutoCleanInterval = const $CopyWithPlaceholder(),
|
||||||
Object? tabViewBottomSheet = const $CopyWithPlaceholder(),
|
Object? tabViewBottomSheet = const $CopyWithPlaceholder(),
|
||||||
Object? tabBarReaderView = const $CopyWithPlaceholder(),
|
Object? tabBarReaderView = const $CopyWithPlaceholder(),
|
||||||
|
Object? tabBarShowContextualBar = const $CopyWithPlaceholder(),
|
||||||
|
Object? tabBarShowQuickTabSwitcherBar = const $CopyWithPlaceholder(),
|
||||||
}) {
|
}) {
|
||||||
return GeneralSettings(
|
return GeneralSettings(
|
||||||
themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null
|
themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null
|
||||||
@@ -264,6 +283,18 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
? _value.tabBarReaderView
|
? _value.tabBarReaderView
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: tabBarReaderView as bool,
|
: tabBarReaderView as bool,
|
||||||
|
tabBarShowContextualBar:
|
||||||
|
tabBarShowContextualBar == const $CopyWithPlaceholder() ||
|
||||||
|
tabBarShowContextualBar == null
|
||||||
|
? _value.tabBarShowContextualBar
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: tabBarShowContextualBar as bool,
|
||||||
|
tabBarShowQuickTabSwitcherBar:
|
||||||
|
tabBarShowQuickTabSwitcherBar == const $CopyWithPlaceholder() ||
|
||||||
|
tabBarShowQuickTabSwitcherBar == null
|
||||||
|
? _value.tabBarShowQuickTabSwitcherBar
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: tabBarShowQuickTabSwitcherBar as bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,46 +310,48 @@ extension $GeneralSettingsCopyWith on GeneralSettings {
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
|
GeneralSettings _$GeneralSettingsFromJson(
|
||||||
GeneralSettings.withDefaults(
|
Map<String, dynamic> json,
|
||||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
) => GeneralSettings.withDefaults(
|
||||||
enableReadability: json['enableReadability'] as bool?,
|
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||||
enforceReadability: json['enforceReadability'] as bool?,
|
enableReadability: json['enableReadability'] as bool?,
|
||||||
deleteBrowsingDataOnQuit:
|
enforceReadability: json['enforceReadability'] as bool?,
|
||||||
(json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
deleteBrowsingDataOnQuit: (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
||||||
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
|
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
|
||||||
.toSet(),
|
.toSet(),
|
||||||
defaultSearchProvider: const BangKeyConverter().fromJson(
|
defaultSearchProvider: const BangKeyConverter().fromJson(
|
||||||
json['defaultSearchProvider'] as String?,
|
json['defaultSearchProvider'] as String?,
|
||||||
),
|
),
|
||||||
defaultSearchSuggestionsProvider: $enumDecodeNullable(
|
defaultSearchSuggestionsProvider: $enumDecodeNullable(
|
||||||
_$SearchSuggestionProvidersEnumMap,
|
_$SearchSuggestionProvidersEnumMap,
|
||||||
json['defaultSearchSuggestionsProvider'],
|
json['defaultSearchSuggestionsProvider'],
|
||||||
),
|
),
|
||||||
createChildTabsOption: json['createChildTabsOption'] as bool?,
|
createChildTabsOption: json['createChildTabsOption'] as bool?,
|
||||||
showExtensionShortcut: json['showExtensionShortcut'] as bool?,
|
showExtensionShortcut: json['showExtensionShortcut'] as bool?,
|
||||||
enableLocalAiFeatures: json['enableLocalAiFeatures'] as bool?,
|
enableLocalAiFeatures: json['enableLocalAiFeatures'] as bool?,
|
||||||
defaultCreateTabType: $enumDecodeNullable(
|
defaultCreateTabType: $enumDecodeNullable(
|
||||||
_$TabTypeEnumMap,
|
_$TabTypeEnumMap,
|
||||||
json['defaultCreateTabType'],
|
json['defaultCreateTabType'],
|
||||||
),
|
),
|
||||||
tabIntentOpenSetting: $enumDecodeNullable(
|
tabIntentOpenSetting: $enumDecodeNullable(
|
||||||
_$TabIntentOpenSettingEnumMap,
|
_$TabIntentOpenSettingEnumMap,
|
||||||
json['tabIntentOpenSetting'],
|
json['tabIntentOpenSetting'],
|
||||||
),
|
),
|
||||||
autoHideTabBar: json['autoHideTabBar'] as bool?,
|
autoHideTabBar: json['autoHideTabBar'] as bool?,
|
||||||
tabBarSwipeAction: $enumDecodeNullable(
|
tabBarSwipeAction: $enumDecodeNullable(
|
||||||
_$TabBarSwipeActionEnumMap,
|
_$TabBarSwipeActionEnumMap,
|
||||||
json['tabBarSwipeAction'],
|
json['tabBarSwipeAction'],
|
||||||
),
|
),
|
||||||
historyAutoCleanInterval: json['historyAutoCleanInterval'] == null
|
historyAutoCleanInterval: json['historyAutoCleanInterval'] == null
|
||||||
? null
|
? null
|
||||||
: Duration(
|
: Duration(
|
||||||
microseconds: (json['historyAutoCleanInterval'] as num).toInt(),
|
microseconds: (json['historyAutoCleanInterval'] as num).toInt(),
|
||||||
),
|
),
|
||||||
tabViewBottomSheet: json['tabViewBottomSheet'] as bool?,
|
tabViewBottomSheet: json['tabViewBottomSheet'] as bool?,
|
||||||
tabBarReaderView: json['tabBarReaderView'] as bool?,
|
tabBarReaderView: json['tabBarReaderView'] as bool?,
|
||||||
);
|
tabBarShowContextualBar: json['tabBarShowContextualBar'] as bool?,
|
||||||
|
tabBarShowQuickTabSwitcherBar: json['tabBarShowQuickTabSwitcherBar'] as bool?,
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$GeneralSettingsToJson(
|
Map<String, dynamic> _$GeneralSettingsToJson(
|
||||||
GeneralSettings instance,
|
GeneralSettings instance,
|
||||||
@@ -346,6 +379,8 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
|||||||
'historyAutoCleanInterval': instance.historyAutoCleanInterval.inMicroseconds,
|
'historyAutoCleanInterval': instance.historyAutoCleanInterval.inMicroseconds,
|
||||||
'tabViewBottomSheet': instance.tabViewBottomSheet,
|
'tabViewBottomSheet': instance.tabViewBottomSheet,
|
||||||
'tabBarReaderView': instance.tabBarReaderView,
|
'tabBarReaderView': instance.tabBarReaderView,
|
||||||
|
'tabBarShowContextualBar': instance.tabBarShowContextualBar,
|
||||||
|
'tabBarShowQuickTabSwitcherBar': instance.tabBarShowQuickTabSwitcherBar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$ThemeModeEnumMap = {
|
const _$ThemeModeEnumMap = {
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
|||||||
DriftSqlType.bool,
|
DriftSqlType.bool,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
),
|
),
|
||||||
|
'tabBarShowContextualBar': settings['tabBarShowContextualBar']?.readAs(
|
||||||
|
DriftSqlType.bool,
|
||||||
|
db.typeMapping,
|
||||||
|
),
|
||||||
|
'tabBarShowQuickTabSwitcherBar': settings['tabBarShowQuickTabSwitcherBar']
|
||||||
|
?.readAs(DriftSqlType.bool, db.typeMapping),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$generalSettingsRepositoryHash() =>
|
String _$generalSettingsRepositoryHash() =>
|
||||||
r'e929fd7a8bf315b08ea7f5a4df2bd5bf066bc84f';
|
r'4014986c9510d6d7210c5c16d814f6b018a1f93c';
|
||||||
|
|
||||||
abstract class _$GeneralSettingsRepository
|
abstract class _$GeneralSettingsRepository
|
||||||
extends $StreamNotifier<GeneralSettings> {
|
extends $StreamNotifier<GeneralSettings> {
|
||||||
|
|||||||
Reference in New Issue
Block a user