tab trees
This commit is contained in:
@@ -28,6 +28,10 @@ part of 'routes.dart';
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
TypedGoRoute<TabTreeRoute>(
|
||||||
|
name: 'TabTreeRoute',
|
||||||
|
path: 'tab_tree/:rootTabId',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class BrowserRoute extends GoRouteData with _$BrowserRoute {
|
class BrowserRoute extends GoRouteData with _$BrowserRoute {
|
||||||
@@ -128,3 +132,14 @@ class ContextMenuRoute extends GoRouteData with _$ContextMenuRoute {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TabTreeRoute extends GoRouteData with _$TabTreeRoute {
|
||||||
|
final String rootTabId;
|
||||||
|
|
||||||
|
const TabTreeRoute(this.rootTabId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
|
return DialogPage(builder: (_) => TabTreeDialog(rootTabId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:weblibre/features/about/presentation/screens/about.dart';
|
|||||||
import 'package:weblibre/features/bangs/presentation/screens/categories.dart';
|
import 'package:weblibre/features/bangs/presentation/screens/categories.dart';
|
||||||
import 'package:weblibre/features/bangs/presentation/screens/list.dart';
|
import 'package:weblibre/features/bangs/presentation/screens/list.dart';
|
||||||
import 'package:weblibre/features/bangs/presentation/screens/search.dart';
|
import 'package:weblibre/features/bangs/presentation/screens/search.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||||
|
|||||||
@@ -300,6 +300,12 @@ RouteBase get $browserRoute => GoRouteData.$route(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
GoRouteData.$route(
|
||||||
|
path: 'tab_tree/:rootTabId',
|
||||||
|
name: 'TabTreeRoute',
|
||||||
|
|
||||||
|
factory: _$TabTreeRoute._fromState,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -500,6 +506,31 @@ mixin _$ContainerEditRoute on GoRouteData {
|
|||||||
context.replace(location, extra: _self.$extra);
|
context.replace(location, extra: _self.$extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin _$TabTreeRoute on GoRouteData {
|
||||||
|
static TabTreeRoute _fromState(GoRouterState state) =>
|
||||||
|
TabTreeRoute(state.pathParameters['rootTabId']!);
|
||||||
|
|
||||||
|
TabTreeRoute get _self => this as TabTreeRoute;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location(
|
||||||
|
'/tab_tree/${Uri.encodeComponent(_self.rootTabId)}',
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
|
|
||||||
extension<T extends Enum> on Map<T, String> {
|
extension<T extends Enum> on Map<T, String> {
|
||||||
T? _$fromName(String? value) =>
|
T? _$fromName(String? value) =>
|
||||||
entries.where((element) => element.value == value).firstOrNull?.key;
|
entries.where((element) => element.value == value).firstOrNull?.key;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class TabState extends WebPageInfo {
|
|||||||
@CopyWithField(immutable: true)
|
@CopyWithField(immutable: true)
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
final String? parentId;
|
||||||
|
|
||||||
final String? contextId;
|
final String? contextId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -47,6 +49,7 @@ class TabState extends WebPageInfo {
|
|||||||
|
|
||||||
TabState({
|
TabState({
|
||||||
required this.id,
|
required this.id,
|
||||||
|
required this.parentId,
|
||||||
required this.contextId,
|
required this.contextId,
|
||||||
required super.url,
|
required super.url,
|
||||||
required String title,
|
required String title,
|
||||||
@@ -64,6 +67,7 @@ class TabState extends WebPageInfo {
|
|||||||
|
|
||||||
factory TabState.$default(String tabId) => TabState(
|
factory TabState.$default(String tabId) => TabState(
|
||||||
id: tabId,
|
id: tabId,
|
||||||
|
parentId: null,
|
||||||
contextId: null,
|
contextId: null,
|
||||||
url: Uri.parse('about:blank'),
|
url: Uri.parse('about:blank'),
|
||||||
title: "",
|
title: "",
|
||||||
@@ -83,6 +87,7 @@ class TabState extends WebPageInfo {
|
|||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
...super.hashParameters,
|
...super.hashParameters,
|
||||||
id,
|
id,
|
||||||
|
parentId,
|
||||||
contextId,
|
contextId,
|
||||||
icon,
|
icon,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ part of 'tab.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
abstract class _$TabStateCWProxy {
|
abstract class _$TabStateCWProxy {
|
||||||
|
TabState parentId(String? parentId);
|
||||||
|
|
||||||
TabState contextId(String? contextId);
|
TabState contextId(String? contextId);
|
||||||
|
|
||||||
TabState url(Uri url);
|
TabState url(Uri url);
|
||||||
@@ -40,6 +42,7 @@ abstract class _$TabStateCWProxy {
|
|||||||
/// TabState(...).copyWith(id: 12, name: "My name")
|
/// TabState(...).copyWith(id: 12, name: "My name")
|
||||||
/// ````
|
/// ````
|
||||||
TabState call({
|
TabState call({
|
||||||
|
String? parentId,
|
||||||
String? contextId,
|
String? contextId,
|
||||||
Uri url,
|
Uri url,
|
||||||
String title,
|
String title,
|
||||||
@@ -62,6 +65,9 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
|
|
||||||
final TabState _value;
|
final TabState _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState parentId(String? parentId) => this(parentId: parentId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TabState contextId(String? contextId) => this(contextId: contextId);
|
TabState contextId(String? contextId) => this(contextId: contextId);
|
||||||
|
|
||||||
@@ -113,6 +119,7 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
/// TabState(...).copyWith(id: 12, name: "My name")
|
/// TabState(...).copyWith(id: 12, name: "My name")
|
||||||
/// ````
|
/// ````
|
||||||
TabState call({
|
TabState call({
|
||||||
|
Object? parentId = const $CopyWithPlaceholder(),
|
||||||
Object? contextId = const $CopyWithPlaceholder(),
|
Object? contextId = const $CopyWithPlaceholder(),
|
||||||
Object? url = const $CopyWithPlaceholder(),
|
Object? url = const $CopyWithPlaceholder(),
|
||||||
Object? title = const $CopyWithPlaceholder(),
|
Object? title = const $CopyWithPlaceholder(),
|
||||||
@@ -129,6 +136,10 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
}) {
|
}) {
|
||||||
return TabState(
|
return TabState(
|
||||||
id: _value.id,
|
id: _value.id,
|
||||||
|
parentId: parentId == const $CopyWithPlaceholder()
|
||||||
|
? _value.parentId
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: parentId as String?,
|
||||||
contextId: contextId == const $CopyWithPlaceholder()
|
contextId: contextId == const $CopyWithPlaceholder()
|
||||||
? _value.contextId
|
? _value.contextId
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class TabStates extends _$TabStates {
|
|||||||
state[contentState.id] ?? TabState.$default(contentState.id);
|
state[contentState.id] ?? TabState.$default(contentState.id);
|
||||||
state = {...state}
|
state = {...state}
|
||||||
..[contentState.id] = current.copyWith(
|
..[contentState.id] = current.copyWith(
|
||||||
|
parentId: contentState.parentId,
|
||||||
contextId: contentState.contextId,
|
contextId: contentState.contextId,
|
||||||
url: Uri.parse(contentState.url),
|
url: Uri.parse(contentState.url),
|
||||||
title: (contentState.title.isNotEmpty)
|
title: (contentState.title.isNotEmpty)
|
||||||
|
|||||||
@@ -53,23 +53,27 @@ class TabRepository extends _$TabRepository {
|
|||||||
.read(selectedContainerProvider.notifier)
|
.read(selectedContainerProvider.notifier)
|
||||||
.fetchData();
|
.fetchData();
|
||||||
|
|
||||||
return ref.read(tabDatabaseProvider).tabDao.upsertContainerTabTransactional(
|
return ref
|
||||||
() {
|
.read(tabDatabaseProvider)
|
||||||
return _tabsService.addTab(
|
.tabDao
|
||||||
url: url,
|
.upsertContainerTabTransactional(
|
||||||
selectTab: selectTab,
|
() {
|
||||||
startLoading: startLoading,
|
return _tabsService.addTab(
|
||||||
parentId: parentId,
|
url: url,
|
||||||
flags: flags,
|
selectTab: selectTab,
|
||||||
contextId: selectedContainer?.metadata.contextualIdentity,
|
startLoading: startLoading,
|
||||||
source: source,
|
parentId: parentId,
|
||||||
private: private,
|
flags: flags,
|
||||||
historyMetadata: historyMetadata,
|
contextId: selectedContainer?.metadata.contextualIdentity,
|
||||||
additionalHeaders: additionalHeaders,
|
source: source,
|
||||||
|
private: private,
|
||||||
|
historyMetadata: historyMetadata,
|
||||||
|
additionalHeaders: additionalHeaders,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
parentId: Value(parentId),
|
||||||
|
containerId: Value(selectedContainer?.id),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
containerId: Value(selectedContainer?.id),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> duplicateTab({
|
Future<String> duplicateTab({
|
||||||
@@ -83,16 +87,20 @@ class TabRepository extends _$TabRepository {
|
|||||||
.getContainerData(containerId),
|
.getContainerData(containerId),
|
||||||
);
|
);
|
||||||
|
|
||||||
return ref.read(tabDatabaseProvider).tabDao.upsertContainerTabTransactional(
|
return ref
|
||||||
() {
|
.read(tabDatabaseProvider)
|
||||||
return _tabsService.duplicateTab(
|
.tabDao
|
||||||
selectTabId: selectTabId,
|
.upsertContainerTabTransactional(
|
||||||
newContextId: containerData?.metadata.contextualIdentity,
|
() {
|
||||||
selectNewTab: selectTab,
|
return _tabsService.duplicateTab(
|
||||||
|
selectTabId: selectTabId,
|
||||||
|
newContextId: containerData?.metadata.contextualIdentity,
|
||||||
|
selectNewTab: selectTab,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
parentId: const Value.absent(),
|
||||||
|
containerId: Value(containerData?.id),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
containerId: Value(containerData?.id),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> selectPreviousTab() async {
|
Future<bool> selectPreviousTab() async {
|
||||||
@@ -227,6 +235,7 @@ class TabRepository extends _$TabRepository {
|
|||||||
final containerId = ref.read(selectedContainerProvider);
|
final containerId = ref.read(selectedContainerProvider);
|
||||||
await db.tabDao.upsertUnassignedTab(
|
await db.tabDao.upsertUnassignedTab(
|
||||||
tabId,
|
tabId,
|
||||||
|
parentId: const Value.absent(),
|
||||||
containerId: Value(containerId),
|
containerId: Value(containerId),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$tabRepositoryHash() => r'9b7ef03bec41f149b3cb456b73d7ee0e1b2804df';
|
String _$tabRepositoryHash() => r'db220096fef5d125a698063e9a88272db92e75c5';
|
||||||
|
|
||||||
/// See also [TabRepository].
|
/// See also [TabRepository].
|
||||||
@ProviderFor(TabRepository)
|
@ProviderFor(TabRepository)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
|
|||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/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/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.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';
|
||||||
|
|
||||||
@@ -87,23 +88,62 @@ EquatableValue<Map<String, TabState>> availableTabStates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
EquatableValue<List<String>> seamlessFilteredTabIds(
|
EquatableValue<List<TabEntity>> seamlessFilteredTabEntities(
|
||||||
Ref ref,
|
Ref ref, {
|
||||||
TabSearchPartition searchPartition,
|
required TabSearchPartition searchPartition,
|
||||||
ContainerFilter containerFilter,
|
required ContainerFilter containerFilter,
|
||||||
) {
|
required bool groupTrees,
|
||||||
|
}) {
|
||||||
final tabSearchResults = ref
|
final tabSearchResults = ref
|
||||||
.watch(
|
.watch(
|
||||||
tabSearchRepositoryProvider(searchPartition).select(
|
tabSearchRepositoryProvider(searchPartition).select(
|
||||||
(value) =>
|
(value) => EquatableValue(
|
||||||
EquatableValue(value.valueOrNull?.map((tab) => tab.id).toList()),
|
value.valueOrNull
|
||||||
|
?.map((tab) => SingleTabEntity(tabId: tab.id))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.value;
|
.value;
|
||||||
|
|
||||||
final availableTabs = ref.watch(availableTabIdsProvider(containerFilter));
|
final availableTabs = ref.watch(
|
||||||
|
availableTabIdsProvider(containerFilter).select(
|
||||||
|
(value) => EquatableValue(
|
||||||
|
value.value.map((tab) => SingleTabEntity(tabId: tab)).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (tabSearchResults == null) {
|
if (tabSearchResults == null) {
|
||||||
|
if (groupTrees) {
|
||||||
|
final trees = ref.watch(
|
||||||
|
tabTreesProvider.select(
|
||||||
|
(value) => EquatableValue(
|
||||||
|
value.valueOrNull
|
||||||
|
?.map(
|
||||||
|
(tree) => TabTreeEntity(
|
||||||
|
tabId: tree.latestTabId,
|
||||||
|
rootId: tree.rootTabId,
|
||||||
|
totalTabs: tree.totalTabs,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return EquatableValue(
|
||||||
|
trees.value
|
||||||
|
.where(
|
||||||
|
(tree) => availableTabs.value.any(
|
||||||
|
(available) => available.tabId == tree.tabId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return availableTabs;
|
return availableTabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -282,32 +282,41 @@ class _AvailableTabStatesProviderElement
|
|||||||
(origin as AvailableTabStatesProvider).containerFilter;
|
(origin as AvailableTabStatesProvider).containerFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$seamlessFilteredTabIdsHash() =>
|
String _$seamlessFilteredTabEntitiesHash() =>
|
||||||
r'171e0b942735066e25894dc2c1d99c367bf0d01a';
|
r'0ef5c14f3fa018e3417e097759817bc871be3e0d';
|
||||||
|
|
||||||
/// See also [seamlessFilteredTabIds].
|
/// See also [seamlessFilteredTabEntities].
|
||||||
@ProviderFor(seamlessFilteredTabIds)
|
@ProviderFor(seamlessFilteredTabEntities)
|
||||||
const seamlessFilteredTabIdsProvider = SeamlessFilteredTabIdsFamily();
|
const seamlessFilteredTabEntitiesProvider = SeamlessFilteredTabEntitiesFamily();
|
||||||
|
|
||||||
/// See also [seamlessFilteredTabIds].
|
/// See also [seamlessFilteredTabEntities].
|
||||||
class SeamlessFilteredTabIdsFamily
|
class SeamlessFilteredTabEntitiesFamily
|
||||||
extends Family<EquatableValue<List<String>>> {
|
extends Family<EquatableValue<List<TabEntity>>> {
|
||||||
/// See also [seamlessFilteredTabIds].
|
/// See also [seamlessFilteredTabEntities].
|
||||||
const SeamlessFilteredTabIdsFamily();
|
const SeamlessFilteredTabEntitiesFamily();
|
||||||
|
|
||||||
/// See also [seamlessFilteredTabIds].
|
/// See also [seamlessFilteredTabEntities].
|
||||||
SeamlessFilteredTabIdsProvider call(
|
SeamlessFilteredTabEntitiesProvider call({
|
||||||
TabSearchPartition searchPartition,
|
required TabSearchPartition searchPartition,
|
||||||
ContainerFilter containerFilter,
|
required ContainerFilter containerFilter,
|
||||||
) {
|
required bool groupTrees,
|
||||||
return SeamlessFilteredTabIdsProvider(searchPartition, containerFilter);
|
}) {
|
||||||
|
return SeamlessFilteredTabEntitiesProvider(
|
||||||
|
searchPartition: searchPartition,
|
||||||
|
containerFilter: containerFilter,
|
||||||
|
groupTrees: groupTrees,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SeamlessFilteredTabIdsProvider getProviderOverride(
|
SeamlessFilteredTabEntitiesProvider getProviderOverride(
|
||||||
covariant SeamlessFilteredTabIdsProvider provider,
|
covariant SeamlessFilteredTabEntitiesProvider provider,
|
||||||
) {
|
) {
|
||||||
return call(provider.searchPartition, provider.containerFilter);
|
return call(
|
||||||
|
searchPartition: provider.searchPartition,
|
||||||
|
containerFilter: provider.containerFilter,
|
||||||
|
groupTrees: provider.groupTrees,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
@@ -322,35 +331,38 @@ class SeamlessFilteredTabIdsFamily
|
|||||||
_allTransitiveDependencies;
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get name => r'seamlessFilteredTabIdsProvider';
|
String? get name => r'seamlessFilteredTabEntitiesProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [seamlessFilteredTabIds].
|
/// See also [seamlessFilteredTabEntities].
|
||||||
class SeamlessFilteredTabIdsProvider
|
class SeamlessFilteredTabEntitiesProvider
|
||||||
extends AutoDisposeProvider<EquatableValue<List<String>>> {
|
extends AutoDisposeProvider<EquatableValue<List<TabEntity>>> {
|
||||||
/// See also [seamlessFilteredTabIds].
|
/// See also [seamlessFilteredTabEntities].
|
||||||
SeamlessFilteredTabIdsProvider(
|
SeamlessFilteredTabEntitiesProvider({
|
||||||
TabSearchPartition searchPartition,
|
required TabSearchPartition searchPartition,
|
||||||
ContainerFilter containerFilter,
|
required ContainerFilter containerFilter,
|
||||||
) : this._internal(
|
required bool groupTrees,
|
||||||
(ref) => seamlessFilteredTabIds(
|
}) : this._internal(
|
||||||
ref as SeamlessFilteredTabIdsRef,
|
(ref) => seamlessFilteredTabEntities(
|
||||||
searchPartition,
|
ref as SeamlessFilteredTabEntitiesRef,
|
||||||
containerFilter,
|
searchPartition: searchPartition,
|
||||||
),
|
containerFilter: containerFilter,
|
||||||
from: seamlessFilteredTabIdsProvider,
|
groupTrees: groupTrees,
|
||||||
name: r'seamlessFilteredTabIdsProvider',
|
),
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
from: seamlessFilteredTabEntitiesProvider,
|
||||||
? null
|
name: r'seamlessFilteredTabEntitiesProvider',
|
||||||
: _$seamlessFilteredTabIdsHash,
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
dependencies: SeamlessFilteredTabIdsFamily._dependencies,
|
? null
|
||||||
allTransitiveDependencies:
|
: _$seamlessFilteredTabEntitiesHash,
|
||||||
SeamlessFilteredTabIdsFamily._allTransitiveDependencies,
|
dependencies: SeamlessFilteredTabEntitiesFamily._dependencies,
|
||||||
searchPartition: searchPartition,
|
allTransitiveDependencies:
|
||||||
containerFilter: containerFilter,
|
SeamlessFilteredTabEntitiesFamily._allTransitiveDependencies,
|
||||||
);
|
searchPartition: searchPartition,
|
||||||
|
containerFilter: containerFilter,
|
||||||
|
groupTrees: groupTrees,
|
||||||
|
);
|
||||||
|
|
||||||
SeamlessFilteredTabIdsProvider._internal(
|
SeamlessFilteredTabEntitiesProvider._internal(
|
||||||
super._createNotifier, {
|
super._createNotifier, {
|
||||||
required super.name,
|
required super.name,
|
||||||
required super.dependencies,
|
required super.dependencies,
|
||||||
@@ -359,20 +371,24 @@ class SeamlessFilteredTabIdsProvider
|
|||||||
required super.from,
|
required super.from,
|
||||||
required this.searchPartition,
|
required this.searchPartition,
|
||||||
required this.containerFilter,
|
required this.containerFilter,
|
||||||
|
required this.groupTrees,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final TabSearchPartition searchPartition;
|
final TabSearchPartition searchPartition;
|
||||||
final ContainerFilter containerFilter;
|
final ContainerFilter containerFilter;
|
||||||
|
final bool groupTrees;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Override overrideWith(
|
Override overrideWith(
|
||||||
EquatableValue<List<String>> Function(SeamlessFilteredTabIdsRef provider)
|
EquatableValue<List<TabEntity>> Function(
|
||||||
|
SeamlessFilteredTabEntitiesRef provider,
|
||||||
|
)
|
||||||
create,
|
create,
|
||||||
) {
|
) {
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SeamlessFilteredTabIdsProvider._internal(
|
override: SeamlessFilteredTabEntitiesProvider._internal(
|
||||||
(ref) => create(ref as SeamlessFilteredTabIdsRef),
|
(ref) => create(ref as SeamlessFilteredTabEntitiesRef),
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
@@ -380,20 +396,22 @@ class SeamlessFilteredTabIdsProvider
|
|||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
searchPartition: searchPartition,
|
searchPartition: searchPartition,
|
||||||
containerFilter: containerFilter,
|
containerFilter: containerFilter,
|
||||||
|
groupTrees: groupTrees,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutoDisposeProviderElement<EquatableValue<List<String>>> createElement() {
|
AutoDisposeProviderElement<EquatableValue<List<TabEntity>>> createElement() {
|
||||||
return _SeamlessFilteredTabIdsProviderElement(this);
|
return _SeamlessFilteredTabEntitiesProviderElement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SeamlessFilteredTabIdsProvider &&
|
return other is SeamlessFilteredTabEntitiesProvider &&
|
||||||
other.searchPartition == searchPartition &&
|
other.searchPartition == searchPartition &&
|
||||||
other.containerFilter == containerFilter;
|
other.containerFilter == containerFilter &&
|
||||||
|
other.groupTrees == groupTrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -401,6 +419,7 @@ class SeamlessFilteredTabIdsProvider
|
|||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
hash = _SystemHash.combine(hash, searchPartition.hashCode);
|
hash = _SystemHash.combine(hash, searchPartition.hashCode);
|
||||||
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, groupTrees.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
@@ -408,26 +427,32 @@ class SeamlessFilteredTabIdsProvider
|
|||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
mixin SeamlessFilteredTabIdsRef
|
mixin SeamlessFilteredTabEntitiesRef
|
||||||
on AutoDisposeProviderRef<EquatableValue<List<String>>> {
|
on AutoDisposeProviderRef<EquatableValue<List<TabEntity>>> {
|
||||||
/// The parameter `searchPartition` of this provider.
|
/// The parameter `searchPartition` of this provider.
|
||||||
TabSearchPartition get searchPartition;
|
TabSearchPartition get searchPartition;
|
||||||
|
|
||||||
/// The parameter `containerFilter` of this provider.
|
/// The parameter `containerFilter` of this provider.
|
||||||
ContainerFilter get containerFilter;
|
ContainerFilter get containerFilter;
|
||||||
|
|
||||||
|
/// The parameter `groupTrees` of this provider.
|
||||||
|
bool get groupTrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SeamlessFilteredTabIdsProviderElement
|
class _SeamlessFilteredTabEntitiesProviderElement
|
||||||
extends AutoDisposeProviderElement<EquatableValue<List<String>>>
|
extends AutoDisposeProviderElement<EquatableValue<List<TabEntity>>>
|
||||||
with SeamlessFilteredTabIdsRef {
|
with SeamlessFilteredTabEntitiesRef {
|
||||||
_SeamlessFilteredTabIdsProviderElement(super.provider);
|
_SeamlessFilteredTabEntitiesProviderElement(super.provider);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TabSearchPartition get searchPartition =>
|
TabSearchPartition get searchPartition =>
|
||||||
(origin as SeamlessFilteredTabIdsProvider).searchPartition;
|
(origin as SeamlessFilteredTabEntitiesProvider).searchPartition;
|
||||||
@override
|
@override
|
||||||
ContainerFilter get containerFilter =>
|
ContainerFilter get containerFilter =>
|
||||||
(origin as SeamlessFilteredTabIdsProvider).containerFilter;
|
(origin as SeamlessFilteredTabEntitiesProvider).containerFilter;
|
||||||
|
@override
|
||||||
|
bool get groupTrees =>
|
||||||
|
(origin as SeamlessFilteredTabEntitiesProvider).groupTrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$seamlessFilteredTabPreviewsHash() =>
|
String _$seamlessFilteredTabPreviewsHash() =>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'tree_view.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class TreeViewController extends _$TreeViewController {
|
||||||
|
void toggle() {
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tree_view.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$treeViewControllerHash() =>
|
||||||
|
r'53da9cc3cc89eab2fee4a3aaf4feccc5ecbfeb20';
|
||||||
|
|
||||||
|
/// See also [TreeViewController].
|
||||||
|
@ProviderFor(TreeViewController)
|
||||||
|
final treeViewControllerProvider =
|
||||||
|
NotifierProvider<TreeViewController, bool>.internal(
|
||||||
|
TreeViewController.new,
|
||||||
|
name: r'treeViewControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$treeViewControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$TreeViewController = Notifier<bool>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:graphview/GraphView.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart' show Vector3;
|
||||||
|
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
||||||
|
|
||||||
|
class TabTreeDialog extends HookConsumerWidget {
|
||||||
|
final String tabId;
|
||||||
|
final Size childSize;
|
||||||
|
|
||||||
|
const TabTreeDialog(
|
||||||
|
this.tabId, {
|
||||||
|
super.key,
|
||||||
|
this.childSize = const Size(200, 300),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final transforamtionController = useTransformationController();
|
||||||
|
|
||||||
|
final builder = useMemoized(() {
|
||||||
|
final config = BuchheimWalkerConfiguration()
|
||||||
|
..siblingSeparation = 100
|
||||||
|
..levelSeparation = 150
|
||||||
|
..subtreeSeparation = 150
|
||||||
|
..orientation = BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM;
|
||||||
|
|
||||||
|
return BuchheimWalkerAlgorithm(config, TreeEdgeRenderer(config));
|
||||||
|
});
|
||||||
|
|
||||||
|
final selectedTabId = ref.watch(selectedTabProvider);
|
||||||
|
final tabs = ref.watch(tabDescendantsProvider(tabId));
|
||||||
|
|
||||||
|
final graph = useMemoized(() {
|
||||||
|
final graph = Graph();
|
||||||
|
graph.isTree = true;
|
||||||
|
|
||||||
|
if (tabs.hasValue) {
|
||||||
|
for (final MapEntry(:key, :value) in tabs.valueOrNull!.entries) {
|
||||||
|
if (value != null) {
|
||||||
|
final current = Node.Id(key);
|
||||||
|
final parent = Node.Id(value);
|
||||||
|
|
||||||
|
graph.addEdge(parent, current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}, [EquatableValue(tabs.valueOrNull)]);
|
||||||
|
|
||||||
|
return Dialog.fullscreen(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: const Color(0x44000000),
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(MdiIcons.target),
|
||||||
|
onPressed: () {
|
||||||
|
final node = graph.getNodeUsingId(selectedTabId);
|
||||||
|
final scale = transforamtionController.value.getMaxScaleOnAxis();
|
||||||
|
final newPosition =
|
||||||
|
(node.position -
|
||||||
|
Offset(childSize.width / 2, childSize.height / 2)) *
|
||||||
|
scale;
|
||||||
|
|
||||||
|
transforamtionController.value =
|
||||||
|
transforamtionController.value.clone()..setTranslation(
|
||||||
|
Vector3(-newPosition.dx, -newPosition.dy, 0),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
|
body: InteractiveViewer(
|
||||||
|
transformationController: transforamtionController,
|
||||||
|
constrained: false,
|
||||||
|
boundaryMargin: const EdgeInsets.all(250),
|
||||||
|
minScale: 0.1,
|
||||||
|
maxScale: 5.0,
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: !tabs.hasValue || tabs.valueOrNull?.isEmpty == true,
|
||||||
|
child: Skeleton.replace(
|
||||||
|
replacement: const Bone.square(),
|
||||||
|
child: GraphView(
|
||||||
|
graph: graph,
|
||||||
|
algorithm: builder,
|
||||||
|
paint: Paint()
|
||||||
|
..color = Theme.of(context).colorScheme.outline
|
||||||
|
..strokeWidth = 1
|
||||||
|
..style = PaintingStyle.stroke,
|
||||||
|
builder: (Node node) {
|
||||||
|
final id = node.key!.value as String;
|
||||||
|
return SizedBox.fromSize(
|
||||||
|
size: childSize,
|
||||||
|
child: SingleTabPreview(
|
||||||
|
tabId: id,
|
||||||
|
activeTabId: selectedTabId,
|
||||||
|
onClose: () {
|
||||||
|
context.pop();
|
||||||
|
ref
|
||||||
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
|
.dismiss();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
|||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tree_view.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_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/draggable_scrollable_header.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
||||||
@@ -385,6 +386,8 @@ class _ViewTabsSheet extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final draggableScrollableController = useDraggableScrollableController();
|
final draggableScrollableController = useDraggableScrollableController();
|
||||||
|
|
||||||
|
final treeModeEnabled = ref.watch(treeViewControllerProvider);
|
||||||
|
|
||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
controller: draggableScrollableController,
|
controller: draggableScrollableController,
|
||||||
expand: false,
|
expand: false,
|
||||||
@@ -396,13 +399,20 @@ class _ViewTabsSheet extends HookConsumerWidget {
|
|||||||
topLeft: Radius.circular(28),
|
topLeft: Radius.circular(28),
|
||||||
topRight: Radius.circular(28),
|
topRight: Radius.circular(28),
|
||||||
),
|
),
|
||||||
child: ViewTabsSheetWidget(
|
child: treeModeEnabled
|
||||||
sheetScrollController: scrollController,
|
? ViewTabTreesSheetWidget(
|
||||||
draggableScrollableController: draggableScrollableController,
|
sheetScrollController: scrollController,
|
||||||
onClose: () {
|
onClose: () {
|
||||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
|
: ViewTabsSheetWidget(
|
||||||
|
sheetScrollController: scrollController,
|
||||||
|
draggableScrollableController: draggableScrollableController,
|
||||||
|
onClose: () {
|
||||||
|
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
+297
-92
@@ -13,9 +13,11 @@ import 'package:weblibre/data/models/drag_data.dart';
|
|||||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.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';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
@@ -28,9 +30,10 @@ import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
|
|||||||
class _TabSheetHeader extends HookConsumerWidget {
|
class _TabSheetHeader extends HookConsumerWidget {
|
||||||
static const headerSize = 124.0;
|
static const headerSize = 124.0;
|
||||||
|
|
||||||
|
final bool treeViewEnabled;
|
||||||
final VoidCallback onClose;
|
final VoidCallback onClose;
|
||||||
|
|
||||||
const _TabSheetHeader({required this.onClose});
|
const _TabSheetHeader({required this.onClose, required this.treeViewEnabled});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -63,13 +66,31 @@ class _TabSheetHeader extends HookConsumerWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
Row(
|
||||||
icon: const Icon(MdiIcons.tabSearch),
|
mainAxisSize: MainAxisSize.min,
|
||||||
label: const Text('Search'),
|
children: [
|
||||||
onPressed: () {
|
IconButton(
|
||||||
searchMode.value = true;
|
icon: const Icon(MdiIcons.tabSearch),
|
||||||
searchTextFocus.requestFocus();
|
iconSize: 18,
|
||||||
},
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
searchMode.value = true;
|
||||||
|
searchTextFocus.requestFocus();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(MdiIcons.graph),
|
||||||
|
selectedIcon: const Icon(MdiIcons.table),
|
||||||
|
isSelected: treeViewEnabled,
|
||||||
|
iconSize: 18,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
ref
|
||||||
|
.read(treeViewControllerProvider.notifier)
|
||||||
|
.toggle();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -116,39 +137,40 @@ class _TabSheetHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
if (!treeViewEnabled)
|
||||||
padding: const EdgeInsets.only(left: 8.0, top: 12),
|
Padding(
|
||||||
child: Consumer(
|
padding: const EdgeInsets.only(left: 8.0, top: 12),
|
||||||
builder: (context, ref, child) {
|
child: Consumer(
|
||||||
final selectedContainer = ref.watch(
|
builder: (context, ref, child) {
|
||||||
selectedContainerDataProvider.select(
|
final selectedContainer = ref.watch(
|
||||||
(value) => value.valueOrNull,
|
selectedContainerDataProvider.select(
|
||||||
),
|
(value) => value.valueOrNull,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return ContainerChips(
|
return ContainerChips(
|
||||||
selectedContainer: selectedContainer,
|
selectedContainer: selectedContainer,
|
||||||
onSelected: (container) async {
|
onSelected: (container) async {
|
||||||
final result = await ref
|
final result = await ref
|
||||||
.read(selectedContainerProvider.notifier)
|
.read(selectedContainerProvider.notifier)
|
||||||
.setContainerId(container.id);
|
.setContainerId(container.id);
|
||||||
|
|
||||||
if (context.mounted &&
|
if (context.mounted &&
|
||||||
result == SetContainerResult.successHasProxy) {
|
result == SetContainerResult.successHasProxy) {
|
||||||
await ref
|
await ref
|
||||||
.read(startProxyControllerProvider.notifier)
|
.read(startProxyControllerProvider.notifier)
|
||||||
.maybeStartProxy(context);
|
.maybeStartProxy(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDeleted: (container) {
|
onDeleted: (container) {
|
||||||
ref
|
ref
|
||||||
.read(selectedContainerProvider.notifier)
|
.read(selectedContainerProvider.notifier)
|
||||||
.clearContainer();
|
.clearContainer();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -212,7 +234,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
DraggableScrollableHeader(
|
DraggableScrollableHeader(
|
||||||
controller: draggableScrollableController,
|
controller: draggableScrollableController,
|
||||||
child: _TabSheetHeader(onClose: onClose),
|
child: _TabSheetHeader(onClose: onClose, treeViewEnabled: false),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: HookConsumer(
|
child: HookConsumer(
|
||||||
@@ -220,11 +242,14 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
final container = ref.watch(selectedContainerProvider);
|
final container = ref.watch(selectedContainerProvider);
|
||||||
|
|
||||||
final filteredTabIds = ref.watch(
|
final filteredTabIds = ref.watch(
|
||||||
seamlessFilteredTabIdsProvider(
|
seamlessFilteredTabEntitiesProvider(
|
||||||
TabSearchPartition.preview,
|
searchPartition: TabSearchPartition.preview,
|
||||||
// ignore: document_ignores using fast equatable
|
// ignore: document_ignores using fast equatable
|
||||||
// ignore: provider_parameters
|
// ignore: provider_parameters
|
||||||
ContainerFilterById(containerId: container),
|
containerFilter: ContainerFilterById(
|
||||||
|
containerId: container,
|
||||||
|
),
|
||||||
|
groupTrees: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -263,7 +288,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final index = filteredTabIds.value.indexWhere(
|
final index = filteredTabIds.value.indexWhere(
|
||||||
(webView) => webView == activeTab,
|
(entity) => entity.tabId == activeTab,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -285,53 +310,58 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
final tabs = useMemoized(() {
|
final tabs = useMemoized(() {
|
||||||
return filteredTabIds.value
|
return filteredTabIds.value
|
||||||
.mapIndexed(
|
.whereType<SingleTabEntity>()
|
||||||
(index, tabId) => CustomDraggable(
|
.mapIndexed((index, entity) {
|
||||||
key: Key(tabId),
|
final child = Consumer(
|
||||||
data: TabDragData(tabId),
|
child: SingleTabPreview(
|
||||||
child: Consumer(
|
tabId: entity.tabId,
|
||||||
child: TabPreviewDraggable(
|
activeTabId: activeTab,
|
||||||
tabId: tabId,
|
onClose: onClose,
|
||||||
activeTabId: activeTab,
|
|
||||||
onClose: onClose,
|
|
||||||
),
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
final dragData = ref.watch(
|
|
||||||
willAcceptDropProvider.select((value) {
|
|
||||||
final dragTabId = switch (value) {
|
|
||||||
ContainerDropData() => value.tabId,
|
|
||||||
DeleteDropData() => value.tabId,
|
|
||||||
null => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (dragTabId == tabId) ? value : null;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return switch (dragData) {
|
|
||||||
ContainerDropData() => Opacity(
|
|
||||||
opacity: 0.3,
|
|
||||||
child: Transform.scale(
|
|
||||||
scale: 0.9,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DeleteDropData() => Opacity(
|
|
||||||
opacity: 0.3,
|
|
||||||
child: ColorFiltered(
|
|
||||||
colorFilter: const ColorFilter.mode(
|
|
||||||
Colors.red,
|
|
||||||
BlendMode.modulate,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
null => child!,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
builder: (context, ref, child) {
|
||||||
)
|
final dragData = ref.watch(
|
||||||
|
willAcceptDropProvider.select((value) {
|
||||||
|
final dragTabId = switch (value) {
|
||||||
|
ContainerDropData() => value.tabId,
|
||||||
|
DeleteDropData() => value.tabId,
|
||||||
|
null => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (dragTabId == entity.tabId)
|
||||||
|
? value
|
||||||
|
: null;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return switch (dragData) {
|
||||||
|
ContainerDropData() => Opacity(
|
||||||
|
opacity: 0.3,
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.9,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DeleteDropData() => Opacity(
|
||||||
|
opacity: 0.3,
|
||||||
|
child: ColorFiltered(
|
||||||
|
colorFilter: const ColorFilter.mode(
|
||||||
|
Colors.red,
|
||||||
|
BlendMode.modulate,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
null => child!,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CustomDraggable(
|
||||||
|
key: Key(entity.tabId),
|
||||||
|
data: TabDragData(entity.tabId),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
})
|
||||||
.toList();
|
.toList();
|
||||||
}, [filteredTabIds, activeTab]);
|
}, [filteredTabIds, activeTab]);
|
||||||
|
|
||||||
@@ -341,7 +371,6 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
//Rebuild when cross axis count changes
|
//Rebuild when cross axis count changes
|
||||||
key: ValueKey(crossAxisCount),
|
key: ValueKey(crossAxisCount),
|
||||||
scrollController: sheetScrollController,
|
scrollController: sheetScrollController,
|
||||||
children: tabs,
|
|
||||||
onDragStarted: (index) {
|
onDragStarted: (index) {
|
||||||
ref.read(willAcceptDropProvider.notifier).clear();
|
ref.read(willAcceptDropProvider.notifier).clear();
|
||||||
},
|
},
|
||||||
@@ -358,7 +387,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
containerRepositoryProvider.notifier,
|
containerRepositoryProvider.notifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
final tabId = filteredTabIds.value[oldIndex];
|
final tabId = filteredTabIds.value[oldIndex].tabId;
|
||||||
final containerId = await ref
|
final containerId = await ref
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
.containerTabId(tabId);
|
.containerTabId(tabId);
|
||||||
@@ -376,7 +405,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
} else {
|
} else {
|
||||||
final orderAfterIndex = newIndex;
|
final orderAfterIndex = newIndex;
|
||||||
key = await containerRepository.getOrderKeyAfterTab(
|
key = await containerRepository.getOrderKeyAfterTab(
|
||||||
filteredTabIds.value[orderAfterIndex],
|
filteredTabIds.value[orderAfterIndex].tabId,
|
||||||
containerId,
|
containerId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -399,6 +428,182 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
itemBuilder: (context, index) => children[index],
|
itemBuilder: (context, index) => children[index],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
children: tabs,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: _TabSheetHeader.headerSize + 4,
|
||||||
|
right: 4,
|
||||||
|
),
|
||||||
|
child: FloatingActionButton.small(
|
||||||
|
onPressed: () async {
|
||||||
|
final isCurrentPrivate =
|
||||||
|
ref.read(selectedTabStateProvider)?.isPrivate ?? false;
|
||||||
|
await SearchRoute(
|
||||||
|
tabType: isCurrentPrivate ? TabType.private : TabType.regular,
|
||||||
|
).push(context);
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewTabTreesSheetWidget extends HookConsumerWidget {
|
||||||
|
final ScrollController sheetScrollController;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
|
||||||
|
const ViewTabTreesSheetWidget({
|
||||||
|
required this.onClose,
|
||||||
|
required this.sheetScrollController,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
int _calculateCrossAxisItemCount({
|
||||||
|
required double screenWidth,
|
||||||
|
required double horizontalPadding,
|
||||||
|
required double crossAxisSpacing,
|
||||||
|
}) {
|
||||||
|
final totalHorizontalPadding = horizontalPadding * 2;
|
||||||
|
final availableWidth =
|
||||||
|
screenWidth - totalHorizontalPadding - crossAxisSpacing;
|
||||||
|
|
||||||
|
final crossAxisCount = availableWidth ~/ 180.0;
|
||||||
|
|
||||||
|
return crossAxisCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size _calculateItemSize({
|
||||||
|
required double screenWidth,
|
||||||
|
required double childAspectRatio,
|
||||||
|
required double horizontalPadding,
|
||||||
|
required double crossAxisSpacing,
|
||||||
|
required int crossAxisCount,
|
||||||
|
}) {
|
||||||
|
final totalHorizontalPadding = horizontalPadding * 2;
|
||||||
|
final totalCrossAxisSpacing = crossAxisSpacing * (crossAxisCount - 1);
|
||||||
|
final availableWidth =
|
||||||
|
screenWidth - totalHorizontalPadding - totalCrossAxisSpacing;
|
||||||
|
final itemWidth = availableWidth / crossAxisCount;
|
||||||
|
final itemHeight = itemWidth / childAspectRatio;
|
||||||
|
|
||||||
|
return Size(itemWidth, itemHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
_TabSheetHeader(onClose: onClose, treeViewEnabled: true),
|
||||||
|
Expanded(
|
||||||
|
child: HookConsumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final filteredTabIds = ref.watch(
|
||||||
|
seamlessFilteredTabEntitiesProvider(
|
||||||
|
searchPartition: TabSearchPartition.preview,
|
||||||
|
// ignore: document_ignores using fast equatable
|
||||||
|
// ignore: provider_parameters
|
||||||
|
containerFilter: ContainerFilterDisabled(),
|
||||||
|
groupTrees: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final activeTab = ref.watch(selectedTabProvider);
|
||||||
|
|
||||||
|
final crossAxisCount = useMemoized(
|
||||||
|
() {
|
||||||
|
final calculatedCount = _calculateCrossAxisItemCount(
|
||||||
|
screenWidth: MediaQuery.of(context).size.width,
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return math.max(
|
||||||
|
math.min(calculatedCount, filteredTabIds.value.length),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
MediaQuery.of(context).size.width,
|
||||||
|
filteredTabIds.value.length,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final itemSize = useMemoized(
|
||||||
|
() =>
|
||||||
|
_calculateItemSize(
|
||||||
|
screenWidth: MediaQuery.of(context).size.width,
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
) +
|
||||||
|
//mainAxisSpacing
|
||||||
|
const Offset(0, 8.0),
|
||||||
|
[MediaQuery.of(context).size.width, crossAxisCount],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
final index = filteredTabIds.value.indexWhere(
|
||||||
|
(entity) => entity.tabId == activeTab,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
final offset = (index ~/ 2) * itemSize.height;
|
||||||
|
|
||||||
|
if (offset != sheetScrollController.offset) {
|
||||||
|
unawaited(
|
||||||
|
sheetScrollController.animateTo(
|
||||||
|
offset,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [filteredTabIds, activeTab]);
|
||||||
|
|
||||||
|
final tabs = useMemoized(() {
|
||||||
|
return filteredTabIds.value.whereType<TabTreeEntity>().map((
|
||||||
|
entity,
|
||||||
|
) {
|
||||||
|
return TabTreePreview(
|
||||||
|
entity: entity,
|
||||||
|
activeTabId: activeTab,
|
||||||
|
onClose: onClose,
|
||||||
|
stackPadding: const Offset(8, 8),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}, [filteredTabIds, activeTab]);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: GridView.builder(
|
||||||
|
controller: sheetScrollController,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
//Sync values for itemHeight calculation _calculateItemHeight
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
mainAxisSpacing: 8.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
),
|
||||||
|
itemCount: tabs.length,
|
||||||
|
itemBuilder: (context, index) => tabs[index],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+221
-89
@@ -1,39 +1,30 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
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:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/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/widgets/tab_icon.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||||
|
|
||||||
class TabPreview extends HookWidget {
|
class _TabBox extends StatelessWidget {
|
||||||
final TabState tab;
|
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
final bool isPrivate;
|
||||||
|
final Widget? child;
|
||||||
|
|
||||||
final VoidCallback? onTap;
|
const _TabBox({required this.isActive, required this.isPrivate, this.child});
|
||||||
final VoidCallback? onDoubleTap;
|
|
||||||
final VoidCallback? onDelete;
|
|
||||||
final void Function(String host)? onDeleteAll;
|
|
||||||
|
|
||||||
const TabPreview({
|
|
||||||
required this.tab,
|
|
||||||
required this.isActive,
|
|
||||||
this.onTap,
|
|
||||||
this.onDoubleTap,
|
|
||||||
this.onDelete,
|
|
||||||
this.onDeleteAll,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
final extendedDeleteMenuController = useMenuController();
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
@@ -43,31 +34,66 @@ class TabPreview extends HookWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: tab.isPrivate
|
color: isPrivate
|
||||||
? const Color(0xFF25003E)
|
? const Color(0xFF25003E)
|
||||||
: colorScheme.surfaceContainerHighest,
|
: colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
|
||||||
child: InkWell(
|
child: child,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
|
),
|
||||||
onTap: onTap,
|
);
|
||||||
onDoubleTap: onDoubleTap,
|
}
|
||||||
child: Column(
|
}
|
||||||
children: [
|
|
||||||
Row(
|
class TabPreview extends HookWidget {
|
||||||
children: [
|
final TabState tab;
|
||||||
Expanded(
|
final bool isActive;
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 6.0, top: 2.0),
|
final VoidCallback? onTap;
|
||||||
child: Text(
|
final VoidCallback? onDoubleTap;
|
||||||
overflow: TextOverflow.ellipsis,
|
final VoidCallback? onLongPress;
|
||||||
tab.title,
|
final VoidCallback? onDelete;
|
||||||
maxLines: 2,
|
final void Function(String host)? onDeleteAll;
|
||||||
style: tab.isPrivate
|
|
||||||
? const TextStyle(color: Colors.white)
|
const TabPreview({
|
||||||
: null,
|
required this.tab,
|
||||||
),
|
required this.isActive,
|
||||||
|
this.onTap,
|
||||||
|
this.onDoubleTap,
|
||||||
|
this.onLongPress,
|
||||||
|
this.onDelete,
|
||||||
|
this.onDeleteAll,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final extendedDeleteMenuController = useMenuController();
|
||||||
|
|
||||||
|
return _TabBox(
|
||||||
|
isActive: isActive,
|
||||||
|
isPrivate: tab.isPrivate,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
|
||||||
|
onTap: onTap,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 6.0, top: 2.0),
|
||||||
|
child: Text(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
tab.title,
|
||||||
|
maxLines: 2,
|
||||||
|
style: tab.isPrivate
|
||||||
|
? const TextStyle(color: Colors.white)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
if (onDelete != null || onDeleteAll != null)
|
||||||
MenuAnchor(
|
MenuAnchor(
|
||||||
controller: extendedDeleteMenuController,
|
controller: extendedDeleteMenuController,
|
||||||
builder: (context, controller, child) {
|
builder: (context, controller, child) {
|
||||||
@@ -100,77 +126,76 @@ class TabPreview extends HookWidget {
|
|||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 6.0),
|
const SizedBox(width: 6.0),
|
||||||
TabIcon(state: tab),
|
TabIcon(state: tab),
|
||||||
const SizedBox(width: 6.0),
|
const SizedBox(width: 6.0),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
tab.url.authority,
|
tab.url.authority,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: tab.isPrivate ? Colors.white : null,
|
color: tab.isPrivate ? Colors.white : null,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (tab.isPrivate) ...[
|
),
|
||||||
const SizedBox(width: 6.0),
|
if (tab.isPrivate) ...[
|
||||||
const SizedBox(
|
const SizedBox(width: 6.0),
|
||||||
height: 16,
|
const SizedBox(
|
||||||
width: 24,
|
height: 16,
|
||||||
child: Stack(
|
width: 24,
|
||||||
fit: StackFit.expand,
|
child: Stack(
|
||||||
children: [
|
fit: StackFit.expand,
|
||||||
Positioned(
|
children: [
|
||||||
top: -4,
|
Positioned(
|
||||||
child: Icon(
|
top: -4,
|
||||||
MdiIcons.dominoMask,
|
child: Icon(
|
||||||
color: Color(0xFF8000D7),
|
MdiIcons.dominoMask,
|
||||||
),
|
color: Color(0xFF8000D7),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(width: 8.0),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
if (tab.thumbnail != null)
|
|
||||||
Expanded(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(14.0),
|
|
||||||
bottomRight: Radius.circular(14.0),
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: RepaintBoundary(
|
|
||||||
child: RawImage(
|
|
||||||
image: tab.thumbnail!.value,
|
|
||||||
fit: BoxFit.fitWidth,
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
if (tab.thumbnail != null)
|
||||||
|
Expanded(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(14.0),
|
||||||
|
bottomRight: Radius.circular(14.0),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: RepaintBoundary(
|
||||||
|
child: RawImage(
|
||||||
|
image: tab.thumbnail!.value,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabPreviewDraggable extends HookConsumerWidget {
|
class SingleTabPreview extends HookConsumerWidget {
|
||||||
final String tabId;
|
final String tabId;
|
||||||
final String? activeTabId;
|
final String? activeTabId;
|
||||||
|
|
||||||
final void Function() onClose;
|
final void Function() onClose;
|
||||||
|
|
||||||
TabPreviewDraggable({
|
SingleTabPreview({
|
||||||
required this.tabId,
|
required this.tabId,
|
||||||
required this.activeTabId,
|
required this.activeTabId,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
@@ -220,3 +245,110 @@ class TabPreviewDraggable extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TabTreePreview extends HookConsumerWidget {
|
||||||
|
final TabTreeEntity entity;
|
||||||
|
final String? activeTabId;
|
||||||
|
|
||||||
|
final Offset stackPadding;
|
||||||
|
|
||||||
|
final void Function() onClose;
|
||||||
|
|
||||||
|
TabTreePreview({
|
||||||
|
required this.entity,
|
||||||
|
required this.activeTabId,
|
||||||
|
required this.onClose,
|
||||||
|
required this.stackPadding,
|
||||||
|
}) : super(key: ValueKey(entity));
|
||||||
|
|
||||||
|
Widget _addPadding(int stackCount, int totalCount, Widget child) {
|
||||||
|
final topPadding = stackPadding * stackCount.toDouble();
|
||||||
|
final bottomPadding = stackPadding * (totalCount - stackCount).toDouble();
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
top: topPadding.dy,
|
||||||
|
left: topPadding.dx,
|
||||||
|
right: bottomPadding.dx,
|
||||||
|
bottom: bottomPadding.dy,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tab = ref.watch(tabStateProvider(entity.tabId));
|
||||||
|
|
||||||
|
if (tab == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final stackCount = math.min(entity.totalTabs, 3) - 1;
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
for (var index = 0; index < stackCount; index++)
|
||||||
|
_addPadding(
|
||||||
|
index,
|
||||||
|
stackCount,
|
||||||
|
_TabBox(
|
||||||
|
isActive: entity.tabId == activeTabId,
|
||||||
|
isPrivate: tab.isPrivate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_addPadding(
|
||||||
|
stackCount,
|
||||||
|
stackCount,
|
||||||
|
Badge.count(
|
||||||
|
isLabelVisible: entity.totalTabs > 1,
|
||||||
|
count: entity.totalTabs,
|
||||||
|
alignment: AlignmentDirectional.bottomEnd,
|
||||||
|
offset: const Offset(-8, -24),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
child: TabPreview(
|
||||||
|
tab: tab,
|
||||||
|
isActive: entity.tabId == activeTabId,
|
||||||
|
onLongPress: () async {
|
||||||
|
if (entity.tabId != activeTabId) {
|
||||||
|
//Close first to avoid rebuilds
|
||||||
|
onClose();
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.selectTab(tab.id);
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () async {
|
||||||
|
if (entity.totalTabs > 1) {
|
||||||
|
await TabTreeRoute(entity.rootId).push(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// onDeleteAll: (host) async {
|
||||||
|
// final containerId = await ref
|
||||||
|
// .read(tabDataRepositoryProvider.notifier)
|
||||||
|
// .containerTabId(tab.id);
|
||||||
|
|
||||||
|
// await ref
|
||||||
|
// .read(tabDataRepositoryProvider.notifier)
|
||||||
|
// .closeAllTabsByHost(containerId, host);
|
||||||
|
// },
|
||||||
|
// onDoubleTap: () {
|
||||||
|
// ref.read(overlayDialogControllerProvider.notifier).show(
|
||||||
|
// TabActionDialog(
|
||||||
|
// initialTab: tab,
|
||||||
|
// onDismiss:
|
||||||
|
// ref.read(overlayDialogControllerProvider.notifier).dismiss,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// onDelete: () async {
|
||||||
|
// await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
|
||||||
|
// },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
|||||||
|
|
||||||
Future<String> upsertContainerTabTransactional(
|
Future<String> upsertContainerTabTransactional(
|
||||||
Future<String> Function() createTab, {
|
Future<String> Function() createTab, {
|
||||||
|
required Value<String?> parentId,
|
||||||
Value<String?> containerId = const Value.absent(),
|
Value<String?> containerId = const Value.absent(),
|
||||||
Value<String?> orderKey = const Value.absent(),
|
Value<String?> orderKey = const Value.absent(),
|
||||||
}) {
|
}) {
|
||||||
@@ -53,12 +54,14 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
|||||||
await db.tab.insertOne(
|
await db.tab.insertOne(
|
||||||
TabCompanion.insert(
|
TabCompanion.insert(
|
||||||
id: tabId,
|
id: tabId,
|
||||||
|
parentId: parentId,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
orderKey: currentOrderKey,
|
orderKey: currentOrderKey,
|
||||||
),
|
),
|
||||||
onConflict: DoUpdate(
|
onConflict: DoUpdate(
|
||||||
(old) => TabCompanion(
|
(old) => TabCompanion(
|
||||||
|
parentId: parentId,
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
orderKey: Value.absentIfNull(orderKey.value),
|
orderKey: Value.absentIfNull(orderKey.value),
|
||||||
),
|
),
|
||||||
@@ -72,6 +75,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
|||||||
//Upsert an tab only if there is no container assigned yet
|
//Upsert an tab only if there is no container assigned yet
|
||||||
Future<String> upsertUnassignedTab(
|
Future<String> upsertUnassignedTab(
|
||||||
String tabId, {
|
String tabId, {
|
||||||
|
required Value<String?> parentId,
|
||||||
Value<String?> containerId = const Value.absent(),
|
Value<String?> containerId = const Value.absent(),
|
||||||
Value<String?> orderKey = const Value.absent(),
|
Value<String?> orderKey = const Value.absent(),
|
||||||
}) {
|
}) {
|
||||||
@@ -85,12 +89,14 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
|||||||
await db.tab.insertOne(
|
await db.tab.insertOne(
|
||||||
TabCompanion.insert(
|
TabCompanion.insert(
|
||||||
id: tabId,
|
id: tabId,
|
||||||
|
parentId: parentId,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
orderKey: currentOrderKey,
|
orderKey: currentOrderKey,
|
||||||
),
|
),
|
||||||
onConflict: DoUpdate(
|
onConflict: DoUpdate(
|
||||||
(old) => TabCompanion(
|
(old) => TabCompanion(
|
||||||
|
parentId: parentId,
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
orderKey: Value.absentIfNull(orderKey.value),
|
orderKey: Value.absentIfNull(orderKey.value),
|
||||||
),
|
),
|
||||||
@@ -152,6 +158,9 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
|||||||
batch.update(
|
batch.update(
|
||||||
db.tab,
|
db.tab,
|
||||||
TabCompanion(
|
TabCompanion(
|
||||||
|
parentId: (previousState?.parentId != state.parentId)
|
||||||
|
? Value(state.parentId)
|
||||||
|
: const Value.absent(),
|
||||||
url: (previousState?.url != state.url)
|
url: (previousState?.url != state.url)
|
||||||
? Value(state.url)
|
? Value(state.url)
|
||||||
: const Value.absent(),
|
: const Value.absent(),
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ CREATE TABLE container (
|
|||||||
metadata TEXT MAPPED BY `const ContainerMetadataConverter()`
|
metadata TEXT MAPPED BY `const ContainerMetadataConverter()`
|
||||||
) WITH ContainerData;
|
) WITH ContainerData;
|
||||||
|
|
||||||
CREATE TABLE tab (
|
CREATE TABLE tab(
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
parent_id TEXT REFERENCES tab (id) ON DELETE SET NULL,
|
||||||
container_id TEXT REFERENCES container (id) ON DELETE CASCADE,
|
container_id TEXT REFERENCES container (id) ON DELETE CASCADE,
|
||||||
order_key TEXT NOT NULL,
|
order_key TEXT NOT NULL,
|
||||||
url TEXT MAPPED BY `const UriConverter()`,
|
url TEXT MAPPED BY `const UriConverter()`,
|
||||||
@@ -35,6 +36,14 @@ CREATE VIRTUAL TABLE tab_fts
|
|||||||
tokenize="trigram"
|
tokenize="trigram"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Create trigger to handle parent deletion
|
||||||
|
CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN
|
||||||
|
-- Update all children of the deleted row to point to its parent
|
||||||
|
UPDATE tab
|
||||||
|
SET parent_id = OLD.parent_id
|
||||||
|
WHERE parent_id = OLD.id;
|
||||||
|
END;
|
||||||
|
|
||||||
-- Triggers to keep the FTS index up to date.
|
-- Triggers to keep the FTS index up to date.
|
||||||
CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN
|
CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN
|
||||||
INSERT INTO
|
INSERT INTO
|
||||||
@@ -159,4 +168,64 @@ queryTabsFullContent WITH TabQueryResult:
|
|||||||
CROSS JOIN weights
|
CROSS JOIN weights
|
||||||
ORDER BY
|
ORDER BY
|
||||||
weighted_rank ASC,
|
weighted_rank ASC,
|
||||||
t.timestamp DESC;
|
t.timestamp DESC;
|
||||||
|
|
||||||
|
tabTrees:
|
||||||
|
WITH RECURSIVE descendants AS (
|
||||||
|
-- Base case: all root tabs (no parent)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
parent_id,
|
||||||
|
timestamp,
|
||||||
|
id AS root_id
|
||||||
|
FROM tab
|
||||||
|
WHERE parent_id IS NULL
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive case: find all descendants
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.parent_id,
|
||||||
|
t.timestamp,
|
||||||
|
d.root_id
|
||||||
|
FROM tab t
|
||||||
|
JOIN descendants d ON t.parent_id = d.id
|
||||||
|
),
|
||||||
|
root_stats AS (
|
||||||
|
-- Calculate stats for each root
|
||||||
|
SELECT
|
||||||
|
root_id,
|
||||||
|
MAX(timestamp) AS max_timestamp,
|
||||||
|
COUNT(*) AS total_children
|
||||||
|
FROM descendants
|
||||||
|
GROUP BY root_id
|
||||||
|
)
|
||||||
|
-- Get the actual tab records with latest timestamp per root
|
||||||
|
SELECT
|
||||||
|
d.root_id AS root_tab_id,
|
||||||
|
d.id AS latest_tab_id,
|
||||||
|
d.timestamp AS latest_timestamp,
|
||||||
|
rs.total_children AS total_tabs
|
||||||
|
FROM descendants d
|
||||||
|
JOIN root_stats rs
|
||||||
|
ON
|
||||||
|
d.root_id = rs.root_id AND
|
||||||
|
d.timestamp = rs.max_timestamp
|
||||||
|
ORDER BY d.timestamp DESC;
|
||||||
|
|
||||||
|
unorderedTabDescendants:
|
||||||
|
WITH RECURSIVE descendants AS (
|
||||||
|
SELECT id, parent_id
|
||||||
|
FROM tab
|
||||||
|
WHERE id = :tab_id
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive case: find all descendants
|
||||||
|
SELECT t.id, t.parent_id
|
||||||
|
FROM tab t
|
||||||
|
JOIN descendants d ON t.parent_id = d.id
|
||||||
|
)
|
||||||
|
SELECT id, parent_id
|
||||||
|
FROM descendants;
|
||||||
@@ -196,6 +196,14 @@ class Tab extends Table with TableInfo<Tab, TabData> {
|
|||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
$customConstraints: 'PRIMARY KEY NOT NULL',
|
$customConstraints: 'PRIMARY KEY NOT NULL',
|
||||||
);
|
);
|
||||||
|
late final GeneratedColumn<String> parentId = GeneratedColumn<String>(
|
||||||
|
'parent_id',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
$customConstraints: 'REFERENCES tab(id)ON DELETE SET NULL',
|
||||||
|
);
|
||||||
late final GeneratedColumn<String> containerId = GeneratedColumn<String>(
|
late final GeneratedColumn<String> containerId = GeneratedColumn<String>(
|
||||||
'container_id',
|
'container_id',
|
||||||
aliasedName,
|
aliasedName,
|
||||||
@@ -283,6 +291,7 @@ class Tab extends Table with TableInfo<Tab, TabData> {
|
|||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [
|
List<GeneratedColumn> get $columns => [
|
||||||
id,
|
id,
|
||||||
|
parentId,
|
||||||
containerId,
|
containerId,
|
||||||
orderKey,
|
orderKey,
|
||||||
url,
|
url,
|
||||||
@@ -309,6 +318,10 @@ class Tab extends Table with TableInfo<Tab, TabData> {
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}id'],
|
data['${effectivePrefix}id'],
|
||||||
)!,
|
)!,
|
||||||
|
parentId: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}parent_id'],
|
||||||
|
),
|
||||||
containerId: attachedDatabase.typeMapping.read(
|
containerId: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}container_id'],
|
data['${effectivePrefix}container_id'],
|
||||||
@@ -368,6 +381,7 @@ class Tab extends Table with TableInfo<Tab, TabData> {
|
|||||||
|
|
||||||
class TabData extends DataClass implements Insertable<TabData> {
|
class TabData extends DataClass implements Insertable<TabData> {
|
||||||
final String id;
|
final String id;
|
||||||
|
final String? parentId;
|
||||||
final String? containerId;
|
final String? containerId;
|
||||||
final String orderKey;
|
final String orderKey;
|
||||||
final Uri? url;
|
final Uri? url;
|
||||||
@@ -380,6 +394,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
const TabData({
|
const TabData({
|
||||||
required this.id,
|
required this.id,
|
||||||
|
this.parentId,
|
||||||
this.containerId,
|
this.containerId,
|
||||||
required this.orderKey,
|
required this.orderKey,
|
||||||
this.url,
|
this.url,
|
||||||
@@ -395,6 +410,9 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
map['id'] = Variable<String>(id);
|
map['id'] = Variable<String>(id);
|
||||||
|
if (!nullToAbsent || parentId != null) {
|
||||||
|
map['parent_id'] = Variable<String>(parentId);
|
||||||
|
}
|
||||||
if (!nullToAbsent || containerId != null) {
|
if (!nullToAbsent || containerId != null) {
|
||||||
map['container_id'] = Variable<String>(containerId);
|
map['container_id'] = Variable<String>(containerId);
|
||||||
}
|
}
|
||||||
@@ -433,6 +451,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return TabData(
|
return TabData(
|
||||||
id: serializer.fromJson<String>(json['id']),
|
id: serializer.fromJson<String>(json['id']),
|
||||||
|
parentId: serializer.fromJson<String?>(json['parent_id']),
|
||||||
containerId: serializer.fromJson<String?>(json['container_id']),
|
containerId: serializer.fromJson<String?>(json['container_id']),
|
||||||
orderKey: serializer.fromJson<String>(json['order_key']),
|
orderKey: serializer.fromJson<String>(json['order_key']),
|
||||||
url: serializer.fromJson<Uri?>(json['url']),
|
url: serializer.fromJson<Uri?>(json['url']),
|
||||||
@@ -460,6 +479,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'id': serializer.toJson<String>(id),
|
'id': serializer.toJson<String>(id),
|
||||||
|
'parent_id': serializer.toJson<String?>(parentId),
|
||||||
'container_id': serializer.toJson<String?>(containerId),
|
'container_id': serializer.toJson<String?>(containerId),
|
||||||
'order_key': serializer.toJson<String>(orderKey),
|
'order_key': serializer.toJson<String>(orderKey),
|
||||||
'url': serializer.toJson<Uri?>(url),
|
'url': serializer.toJson<Uri?>(url),
|
||||||
@@ -479,6 +499,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
|
|
||||||
TabData copyWith({
|
TabData copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
|
Value<String?> parentId = const Value.absent(),
|
||||||
Value<String?> containerId = const Value.absent(),
|
Value<String?> containerId = const Value.absent(),
|
||||||
String? orderKey,
|
String? orderKey,
|
||||||
Value<Uri?> url = const Value.absent(),
|
Value<Uri?> url = const Value.absent(),
|
||||||
@@ -491,6 +512,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
DateTime? timestamp,
|
DateTime? timestamp,
|
||||||
}) => TabData(
|
}) => TabData(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
parentId: parentId.present ? parentId.value : this.parentId,
|
||||||
containerId: containerId.present ? containerId.value : this.containerId,
|
containerId: containerId.present ? containerId.value : this.containerId,
|
||||||
orderKey: orderKey ?? this.orderKey,
|
orderKey: orderKey ?? this.orderKey,
|
||||||
url: url.present ? url.value : this.url,
|
url: url.present ? url.value : this.url,
|
||||||
@@ -515,6 +537,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
TabData copyWithCompanion(TabCompanion data) {
|
TabData copyWithCompanion(TabCompanion data) {
|
||||||
return TabData(
|
return TabData(
|
||||||
id: data.id.present ? data.id.value : this.id,
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
parentId: data.parentId.present ? data.parentId.value : this.parentId,
|
||||||
containerId: data.containerId.present
|
containerId: data.containerId.present
|
||||||
? data.containerId.value
|
? data.containerId.value
|
||||||
: this.containerId,
|
: this.containerId,
|
||||||
@@ -544,6 +567,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('TabData(')
|
return (StringBuffer('TabData(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
|
..write('parentId: $parentId, ')
|
||||||
..write('containerId: $containerId, ')
|
..write('containerId: $containerId, ')
|
||||||
..write('orderKey: $orderKey, ')
|
..write('orderKey: $orderKey, ')
|
||||||
..write('url: $url, ')
|
..write('url: $url, ')
|
||||||
@@ -561,6 +585,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
id,
|
id,
|
||||||
|
parentId,
|
||||||
containerId,
|
containerId,
|
||||||
orderKey,
|
orderKey,
|
||||||
url,
|
url,
|
||||||
@@ -577,6 +602,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is TabData &&
|
(other is TabData &&
|
||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
|
other.parentId == this.parentId &&
|
||||||
other.containerId == this.containerId &&
|
other.containerId == this.containerId &&
|
||||||
other.orderKey == this.orderKey &&
|
other.orderKey == this.orderKey &&
|
||||||
other.url == this.url &&
|
other.url == this.url &&
|
||||||
@@ -591,6 +617,7 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
|
|
||||||
class TabCompanion extends UpdateCompanion<TabData> {
|
class TabCompanion extends UpdateCompanion<TabData> {
|
||||||
final Value<String> id;
|
final Value<String> id;
|
||||||
|
final Value<String?> parentId;
|
||||||
final Value<String?> containerId;
|
final Value<String?> containerId;
|
||||||
final Value<String> orderKey;
|
final Value<String> orderKey;
|
||||||
final Value<Uri?> url;
|
final Value<Uri?> url;
|
||||||
@@ -604,6 +631,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const TabCompanion({
|
const TabCompanion({
|
||||||
this.id = const Value.absent(),
|
this.id = const Value.absent(),
|
||||||
|
this.parentId = const Value.absent(),
|
||||||
this.containerId = const Value.absent(),
|
this.containerId = const Value.absent(),
|
||||||
this.orderKey = const Value.absent(),
|
this.orderKey = const Value.absent(),
|
||||||
this.url = const Value.absent(),
|
this.url = const Value.absent(),
|
||||||
@@ -618,6 +646,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
});
|
});
|
||||||
TabCompanion.insert({
|
TabCompanion.insert({
|
||||||
required String id,
|
required String id,
|
||||||
|
this.parentId = const Value.absent(),
|
||||||
this.containerId = const Value.absent(),
|
this.containerId = const Value.absent(),
|
||||||
required String orderKey,
|
required String orderKey,
|
||||||
this.url = const Value.absent(),
|
this.url = const Value.absent(),
|
||||||
@@ -634,6 +663,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
timestamp = Value(timestamp);
|
timestamp = Value(timestamp);
|
||||||
static Insertable<TabData> custom({
|
static Insertable<TabData> custom({
|
||||||
Expression<String>? id,
|
Expression<String>? id,
|
||||||
|
Expression<String>? parentId,
|
||||||
Expression<String>? containerId,
|
Expression<String>? containerId,
|
||||||
Expression<String>? orderKey,
|
Expression<String>? orderKey,
|
||||||
Expression<String>? url,
|
Expression<String>? url,
|
||||||
@@ -648,6 +678,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
|
if (parentId != null) 'parent_id': parentId,
|
||||||
if (containerId != null) 'container_id': containerId,
|
if (containerId != null) 'container_id': containerId,
|
||||||
if (orderKey != null) 'order_key': orderKey,
|
if (orderKey != null) 'order_key': orderKey,
|
||||||
if (url != null) 'url': url,
|
if (url != null) 'url': url,
|
||||||
@@ -668,6 +699,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
|
|
||||||
TabCompanion copyWith({
|
TabCompanion copyWith({
|
||||||
Value<String>? id,
|
Value<String>? id,
|
||||||
|
Value<String?>? parentId,
|
||||||
Value<String?>? containerId,
|
Value<String?>? containerId,
|
||||||
Value<String>? orderKey,
|
Value<String>? orderKey,
|
||||||
Value<Uri?>? url,
|
Value<Uri?>? url,
|
||||||
@@ -682,6 +714,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
}) {
|
}) {
|
||||||
return TabCompanion(
|
return TabCompanion(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
parentId: parentId ?? this.parentId,
|
||||||
containerId: containerId ?? this.containerId,
|
containerId: containerId ?? this.containerId,
|
||||||
orderKey: orderKey ?? this.orderKey,
|
orderKey: orderKey ?? this.orderKey,
|
||||||
url: url ?? this.url,
|
url: url ?? this.url,
|
||||||
@@ -704,6 +737,9 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
if (id.present) {
|
if (id.present) {
|
||||||
map['id'] = Variable<String>(id.value);
|
map['id'] = Variable<String>(id.value);
|
||||||
}
|
}
|
||||||
|
if (parentId.present) {
|
||||||
|
map['parent_id'] = Variable<String>(parentId.value);
|
||||||
|
}
|
||||||
if (containerId.present) {
|
if (containerId.present) {
|
||||||
map['container_id'] = Variable<String>(containerId.value);
|
map['container_id'] = Variable<String>(containerId.value);
|
||||||
}
|
}
|
||||||
@@ -752,6 +788,7 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('TabCompanion(')
|
return (StringBuffer('TabCompanion(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
|
..write('parentId: $parentId, ')
|
||||||
..write('containerId: $containerId, ')
|
..write('containerId: $containerId, ')
|
||||||
..write('orderKey: $orderKey, ')
|
..write('orderKey: $orderKey, ')
|
||||||
..write('url: $url, ')
|
..write('url: $url, ')
|
||||||
@@ -1051,6 +1088,10 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
late final Container container = Container(this);
|
late final Container container = Container(this);
|
||||||
late final Tab tab = Tab(this);
|
late final Tab tab = Tab(this);
|
||||||
late final TabFts tabFts = TabFts(this);
|
late final TabFts tabFts = TabFts(this);
|
||||||
|
late final Trigger tabMaintainParentChainOnDelete = Trigger(
|
||||||
|
'CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = OLD.parent_id WHERE parent_id = OLD.id;END',
|
||||||
|
'tab_maintain_parent_chain_on_delete',
|
||||||
|
);
|
||||||
late final Trigger tabAfterInsert = Trigger(
|
late final Trigger tabAfterInsert = Trigger(
|
||||||
'CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END',
|
'CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END',
|
||||||
'tab_after_insert',
|
'tab_after_insert',
|
||||||
@@ -1177,6 +1218,36 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Selectable<TabTreesResult> tabTrees() {
|
||||||
|
return customSelect(
|
||||||
|
'WITH RECURSIVE descendants AS (SELECT id, parent_id, timestamp, id AS root_id FROM tab WHERE parent_id IS NULL UNION ALL SELECT t.id, t.parent_id, t.timestamp, d.root_id FROM tab AS t JOIN descendants AS d ON t.parent_id = d.id), root_stats AS (SELECT root_id, MAX(timestamp) AS max_timestamp, COUNT(*) AS total_children FROM descendants GROUP BY root_id) SELECT d.root_id AS root_tab_id, d.id AS latest_tab_id, d.timestamp AS latest_timestamp, rs.total_children AS total_tabs FROM descendants AS d JOIN root_stats AS rs ON d.root_id = rs.root_id AND d.timestamp = rs.max_timestamp ORDER BY d.timestamp DESC',
|
||||||
|
variables: [],
|
||||||
|
readsFrom: {tab},
|
||||||
|
).map(
|
||||||
|
(QueryRow row) => TabTreesResult(
|
||||||
|
rootTabId: row.read<String>('root_tab_id'),
|
||||||
|
latestTabId: row.read<String>('latest_tab_id'),
|
||||||
|
latestTimestamp: row.read<DateTime>('latest_timestamp'),
|
||||||
|
totalTabs: row.read<int>('total_tabs'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Selectable<UnorderedTabDescendantsResult> unorderedTabDescendants({
|
||||||
|
required String tabId,
|
||||||
|
}) {
|
||||||
|
return customSelect(
|
||||||
|
'WITH RECURSIVE descendants AS (SELECT id, parent_id FROM tab WHERE id = ?1 UNION ALL SELECT t.id, t.parent_id FROM tab AS t JOIN descendants AS d ON t.parent_id = d.id) SELECT id, parent_id FROM descendants',
|
||||||
|
variables: [Variable<String>(tabId)],
|
||||||
|
readsFrom: {tab},
|
||||||
|
).map(
|
||||||
|
(QueryRow row) => UnorderedTabDescendantsResult(
|
||||||
|
id: row.read<String>('id'),
|
||||||
|
parentId: row.readNullable<String>('parent_id'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
@@ -1185,6 +1256,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
container,
|
container,
|
||||||
tab,
|
tab,
|
||||||
tabFts,
|
tabFts,
|
||||||
|
tabMaintainParentChainOnDelete,
|
||||||
tabAfterInsert,
|
tabAfterInsert,
|
||||||
tabAfterDelete,
|
tabAfterDelete,
|
||||||
tabAfterUpdate,
|
tabAfterUpdate,
|
||||||
@@ -1198,6 +1270,13 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
),
|
),
|
||||||
result: [TableUpdate('tab', kind: UpdateKind.delete)],
|
result: [TableUpdate('tab', kind: UpdateKind.delete)],
|
||||||
),
|
),
|
||||||
|
WritePropagation(
|
||||||
|
on: TableUpdateQuery.onTableName(
|
||||||
|
'tab',
|
||||||
|
limitUpdateKind: UpdateKind.delete,
|
||||||
|
),
|
||||||
|
result: [TableUpdate('tab', kind: UpdateKind.update)],
|
||||||
|
),
|
||||||
WritePropagation(
|
WritePropagation(
|
||||||
on: TableUpdateQuery.onTableName(
|
on: TableUpdateQuery.onTableName(
|
||||||
'tab',
|
'tab',
|
||||||
@@ -1499,6 +1578,7 @@ typedef $ContainerProcessedTableManager =
|
|||||||
typedef $TabCreateCompanionBuilder =
|
typedef $TabCreateCompanionBuilder =
|
||||||
TabCompanion Function({
|
TabCompanion Function({
|
||||||
required String id,
|
required String id,
|
||||||
|
Value<String?> parentId,
|
||||||
Value<String?> containerId,
|
Value<String?> containerId,
|
||||||
required String orderKey,
|
required String orderKey,
|
||||||
Value<Uri?> url,
|
Value<Uri?> url,
|
||||||
@@ -1514,6 +1594,7 @@ typedef $TabCreateCompanionBuilder =
|
|||||||
typedef $TabUpdateCompanionBuilder =
|
typedef $TabUpdateCompanionBuilder =
|
||||||
TabCompanion Function({
|
TabCompanion Function({
|
||||||
Value<String> id,
|
Value<String> id,
|
||||||
|
Value<String?> parentId,
|
||||||
Value<String?> containerId,
|
Value<String?> containerId,
|
||||||
Value<String> orderKey,
|
Value<String> orderKey,
|
||||||
Value<Uri?> url,
|
Value<Uri?> url,
|
||||||
@@ -1561,6 +1642,11 @@ class $TabFilterComposer extends Composer<_$TabDatabase, Tab> {
|
|||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get parentId => $composableBuilder(
|
||||||
|
column: $table.parentId,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnFilters<String> get orderKey => $composableBuilder(
|
ColumnFilters<String> get orderKey => $composableBuilder(
|
||||||
column: $table.orderKey,
|
column: $table.orderKey,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
@@ -1644,6 +1730,11 @@ class $TabOrderingComposer extends Composer<_$TabDatabase, Tab> {
|
|||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get parentId => $composableBuilder(
|
||||||
|
column: $table.parentId,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnOrderings<String> get orderKey => $composableBuilder(
|
ColumnOrderings<String> get orderKey => $composableBuilder(
|
||||||
column: $table.orderKey,
|
column: $table.orderKey,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
@@ -1724,6 +1815,9 @@ class $TabAnnotationComposer extends Composer<_$TabDatabase, Tab> {
|
|||||||
GeneratedColumn<String> get id =>
|
GeneratedColumn<String> get id =>
|
||||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get parentId =>
|
||||||
|
$composableBuilder(column: $table.parentId, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<String> get orderKey =>
|
GeneratedColumn<String> get orderKey =>
|
||||||
$composableBuilder(column: $table.orderKey, builder: (column) => column);
|
$composableBuilder(column: $table.orderKey, builder: (column) => column);
|
||||||
|
|
||||||
@@ -1814,6 +1908,7 @@ class $TabTableManager
|
|||||||
updateCompanionCallback:
|
updateCompanionCallback:
|
||||||
({
|
({
|
||||||
Value<String> id = const Value.absent(),
|
Value<String> id = const Value.absent(),
|
||||||
|
Value<String?> parentId = const Value.absent(),
|
||||||
Value<String?> containerId = const Value.absent(),
|
Value<String?> containerId = const Value.absent(),
|
||||||
Value<String> orderKey = const Value.absent(),
|
Value<String> orderKey = const Value.absent(),
|
||||||
Value<Uri?> url = const Value.absent(),
|
Value<Uri?> url = const Value.absent(),
|
||||||
@@ -1827,6 +1922,7 @@ class $TabTableManager
|
|||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => TabCompanion(
|
}) => TabCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
|
parentId: parentId,
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
orderKey: orderKey,
|
orderKey: orderKey,
|
||||||
url: url,
|
url: url,
|
||||||
@@ -1842,6 +1938,7 @@ class $TabTableManager
|
|||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
required String id,
|
required String id,
|
||||||
|
Value<String?> parentId = const Value.absent(),
|
||||||
Value<String?> containerId = const Value.absent(),
|
Value<String?> containerId = const Value.absent(),
|
||||||
required String orderKey,
|
required String orderKey,
|
||||||
Value<Uri?> url = const Value.absent(),
|
Value<Uri?> url = const Value.absent(),
|
||||||
@@ -1855,6 +1952,7 @@ class $TabTableManager
|
|||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => TabCompanion.insert(
|
}) => TabCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
|
parentId: parentId,
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
orderKey: orderKey,
|
orderKey: orderKey,
|
||||||
url: url,
|
url: url,
|
||||||
@@ -2114,3 +2212,22 @@ class $TabDatabaseManager {
|
|||||||
$TabTableManager get tab => $TabTableManager(_db, _db.tab);
|
$TabTableManager get tab => $TabTableManager(_db, _db.tab);
|
||||||
$TabFtsTableManager get tabFts => $TabFtsTableManager(_db, _db.tabFts);
|
$TabFtsTableManager get tabFts => $TabFtsTableManager(_db, _db.tabFts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TabTreesResult {
|
||||||
|
final String rootTabId;
|
||||||
|
final String latestTabId;
|
||||||
|
final DateTime latestTimestamp;
|
||||||
|
final int totalTabs;
|
||||||
|
TabTreesResult({
|
||||||
|
required this.rootTabId,
|
||||||
|
required this.latestTabId,
|
||||||
|
required this.latestTimestamp,
|
||||||
|
required this.totalTabs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnorderedTabDescendantsResult {
|
||||||
|
final String id;
|
||||||
|
final String? parentId;
|
||||||
|
UnorderedTabDescendantsResult({required this.id, this.parentId});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
|
||||||
|
sealed class TabEntity with FastEquatable {
|
||||||
|
String get tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleTabEntity extends TabEntity {
|
||||||
|
@override
|
||||||
|
final String tabId;
|
||||||
|
|
||||||
|
SingleTabEntity({required this.tabId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [tabId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabTreeEntity extends TabEntity {
|
||||||
|
@override
|
||||||
|
final String tabId;
|
||||||
|
|
||||||
|
final String rootId;
|
||||||
|
|
||||||
|
final int totalTabs;
|
||||||
|
|
||||||
|
TabTreeEntity({
|
||||||
|
required this.tabId,
|
||||||
|
required this.rootId,
|
||||||
|
required this.totalTabs,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [tabId, rootId, totalTabs];
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:riverpod/riverpod.dart';
|
import 'package:riverpod/riverpod.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.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/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';
|
||||||
@@ -47,3 +48,19 @@ Stream<List<String>> containerTabIds(Ref ref, ContainerFilter containerFilter) {
|
|||||||
return db.tabDao.getAllTabIds().watch();
|
return db.tabDao.getAllTabIds().watch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Stream<List<TabTreesResult>> tabTrees(Ref ref) {
|
||||||
|
final db = ref.watch(tabDatabaseProvider);
|
||||||
|
return db.tabTrees().watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Stream<Map<String, String?>> tabDescendants(Ref ref, String tabId) {
|
||||||
|
final db = ref.watch(tabDatabaseProvider);
|
||||||
|
return db.unorderedTabDescendants(tabId: tabId).watch().map((results) {
|
||||||
|
return Map.fromEntries(
|
||||||
|
results.map((pair) => MapEntry(pair.id, pair.parentId)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -303,5 +303,144 @@ class _ContainerTabIdsProviderElement
|
|||||||
(origin as ContainerTabIdsProvider).containerFilter;
|
(origin as ContainerTabIdsProvider).containerFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _$tabTreesHash() => r'b7b7f7136827207dd01a7894a915be3d17b1ae63';
|
||||||
|
|
||||||
|
/// See also [tabTrees].
|
||||||
|
@ProviderFor(tabTrees)
|
||||||
|
final tabTreesProvider =
|
||||||
|
AutoDisposeStreamProvider<List<TabTreesResult>>.internal(
|
||||||
|
tabTrees,
|
||||||
|
name: r'tabTreesProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$tabTreesHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef TabTreesRef = AutoDisposeStreamProviderRef<List<TabTreesResult>>;
|
||||||
|
String _$tabDescendantsHash() => r'93e19df9896e4876dc911370c02bf10f1ee9a9e6';
|
||||||
|
|
||||||
|
/// See also [tabDescendants].
|
||||||
|
@ProviderFor(tabDescendants)
|
||||||
|
const tabDescendantsProvider = TabDescendantsFamily();
|
||||||
|
|
||||||
|
/// See also [tabDescendants].
|
||||||
|
class TabDescendantsFamily extends Family<AsyncValue<Map<String, String?>>> {
|
||||||
|
/// See also [tabDescendants].
|
||||||
|
const TabDescendantsFamily();
|
||||||
|
|
||||||
|
/// See also [tabDescendants].
|
||||||
|
TabDescendantsProvider call(String tabId) {
|
||||||
|
return TabDescendantsProvider(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabDescendantsProvider getProviderOverride(
|
||||||
|
covariant TabDescendantsProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'tabDescendantsProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [tabDescendants].
|
||||||
|
class TabDescendantsProvider
|
||||||
|
extends AutoDisposeStreamProvider<Map<String, String?>> {
|
||||||
|
/// See also [tabDescendants].
|
||||||
|
TabDescendantsProvider(String tabId)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => tabDescendants(ref as TabDescendantsRef, tabId),
|
||||||
|
from: tabDescendantsProvider,
|
||||||
|
name: r'tabDescendantsProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$tabDescendantsHash,
|
||||||
|
dependencies: TabDescendantsFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
TabDescendantsFamily._allTransitiveDependencies,
|
||||||
|
tabId: tabId,
|
||||||
|
);
|
||||||
|
|
||||||
|
TabDescendantsProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.tabId,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String tabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
Stream<Map<String, String?>> Function(TabDescendantsRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: TabDescendantsProvider._internal(
|
||||||
|
(ref) => create(ref as TabDescendantsRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
tabId: tabId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeStreamProviderElement<Map<String, String?>> createElement() {
|
||||||
|
return _TabDescendantsProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is TabDescendantsProvider && other.tabId == tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, tabId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin TabDescendantsRef on AutoDisposeStreamProviderRef<Map<String, String?>> {
|
||||||
|
/// The parameter `tabId` of this provider.
|
||||||
|
String get tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TabDescendantsProviderElement
|
||||||
|
extends AutoDisposeStreamProviderElement<Map<String, String?>>
|
||||||
|
with TabDescendantsRef {
|
||||||
|
_TabDescendantsProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tabId => (origin as TabDescendantsProvider).tabId;
|
||||||
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ dependencies:
|
|||||||
flutter_svg: ^2.1.0
|
flutter_svg: ^2.1.0
|
||||||
go_router: ^15.2.0
|
go_router: ^15.2.0
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
|
graphview: ^1.2.0
|
||||||
home_widget: ^0.8.0
|
home_widget: ^0.8.0
|
||||||
hooks_riverpod: ^2.6.1
|
hooks_riverpod: ^2.6.1
|
||||||
html: ^0.15.6
|
html: ^0.15.6
|
||||||
@@ -75,6 +76,7 @@ dependencies:
|
|||||||
url: https://github.com/FaFre/uri-to-file.git
|
url: https://github.com/FaFre/uri-to-file.git
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
|
vector_math: ^2.1.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
analyzer_plugin: ^0.13.1
|
analyzer_plugin: ^0.13.1
|
||||||
|
|||||||
+1
@@ -185,6 +185,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
|||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
TabContentState(
|
TabContentState(
|
||||||
id = tab.id,
|
id = tab.id,
|
||||||
|
parentId = tab.parentId,
|
||||||
contextId = tab.contextId,
|
contextId = tab.contextId,
|
||||||
url = tab.content.url,
|
url = tab.content.url,
|
||||||
title = tab.content.title,
|
title = tab.content.title,
|
||||||
|
|||||||
+1
@@ -158,6 +158,7 @@ class Events(
|
|||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
TabContentState(
|
TabContentState(
|
||||||
id = tab.id,
|
id = tab.id,
|
||||||
|
parentId = tab.parentId,
|
||||||
contextId = tab.contextId,
|
contextId = tab.contextId,
|
||||||
url = tab.content.url,
|
url = tab.content.url,
|
||||||
title = tab.content.title,
|
title = tab.content.title,
|
||||||
|
|||||||
+12
-9
@@ -1,4 +1,4 @@
|
|||||||
// Autogenerated from Pigeon (v25.3.1), do not edit directly.
|
// Autogenerated from Pigeon (v25.3.2), do not edit directly.
|
||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
@@ -1194,6 +1194,7 @@ data class SecurityInfoState (
|
|||||||
/** Generated class from Pigeon that represents data sent in messages. */
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
data class TabContentState (
|
data class TabContentState (
|
||||||
val id: String,
|
val id: String,
|
||||||
|
val parentId: String? = null,
|
||||||
val contextId: String? = null,
|
val contextId: String? = null,
|
||||||
val url: String,
|
val url: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
@@ -1206,19 +1207,21 @@ data class TabContentState (
|
|||||||
companion object {
|
companion object {
|
||||||
fun fromList(pigeonVar_list: List<Any?>): TabContentState {
|
fun fromList(pigeonVar_list: List<Any?>): TabContentState {
|
||||||
val id = pigeonVar_list[0] as String
|
val id = pigeonVar_list[0] as String
|
||||||
val contextId = pigeonVar_list[1] as String?
|
val parentId = pigeonVar_list[1] as String?
|
||||||
val url = pigeonVar_list[2] as String
|
val contextId = pigeonVar_list[2] as String?
|
||||||
val title = pigeonVar_list[3] as String
|
val url = pigeonVar_list[3] as String
|
||||||
val progress = pigeonVar_list[4] as Long
|
val title = pigeonVar_list[4] as String
|
||||||
val isPrivate = pigeonVar_list[5] as Boolean
|
val progress = pigeonVar_list[5] as Long
|
||||||
val isFullScreen = pigeonVar_list[6] as Boolean
|
val isPrivate = pigeonVar_list[6] as Boolean
|
||||||
val isLoading = pigeonVar_list[7] as Boolean
|
val isFullScreen = pigeonVar_list[7] as Boolean
|
||||||
return TabContentState(id, contextId, url, title, progress, isPrivate, isFullScreen, isLoading)
|
val isLoading = pigeonVar_list[8] as Boolean
|
||||||
|
return TabContentState(id, parentId, contextId, url, title, progress, isPrivate, isFullScreen, isLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
return listOf(
|
return listOf(
|
||||||
id,
|
id,
|
||||||
|
parentId,
|
||||||
contextId,
|
contextId,
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -419,6 +419,7 @@ class SecurityInfoState {
|
|||||||
|
|
||||||
class TabContentState {
|
class TabContentState {
|
||||||
final String id;
|
final String id;
|
||||||
|
final String? parentId;
|
||||||
final String? contextId;
|
final String? contextId;
|
||||||
|
|
||||||
final String url;
|
final String url;
|
||||||
@@ -432,6 +433,7 @@ class TabContentState {
|
|||||||
|
|
||||||
TabContentState(
|
TabContentState(
|
||||||
this.id,
|
this.id,
|
||||||
|
this.parentId,
|
||||||
this.contextId,
|
this.contextId,
|
||||||
this.url,
|
this.url,
|
||||||
this.title,
|
this.title,
|
||||||
|
|||||||
Reference in New Issue
Block a user