intermediate
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -97,3 +99,34 @@ List<String> availableTabIds(
|
||||
return containerTabs?.where((tabId) => tabStates.contains(tabId)).toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
List<String> seamlessFilteredTabs(
|
||||
Ref ref,
|
||||
String? containerId,
|
||||
) {
|
||||
final tabSearchResults = ref
|
||||
.watch(
|
||||
tabSearchRepositoryProvider.select(
|
||||
(value) => EquatableCollection(
|
||||
value.valueOrNull?.map((tab) => tab.id).toList(),
|
||||
immutable: true,
|
||||
),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
final availableTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(containerId).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
if (tabSearchResults == null) {
|
||||
return availableTabs;
|
||||
}
|
||||
|
||||
return tabSearchResults.where((tab) => availableTabs.contains(tab)).toList();
|
||||
}
|
||||
|
||||
@@ -288,6 +288,140 @@ class _AvailableTabIdsProviderElement
|
||||
String? get containerId => (origin as AvailableTabIdsProvider).containerId;
|
||||
}
|
||||
|
||||
String _$seamlessFilteredTabsHash() =>
|
||||
r'1abfb0e9ae834cdde5ab64b6174f24f45f36a272';
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
@ProviderFor(seamlessFilteredTabs)
|
||||
const seamlessFilteredTabsProvider = SeamlessFilteredTabsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
class SeamlessFilteredTabsFamily extends Family<List<String>> {
|
||||
/// See also [seamlessFilteredTabs].
|
||||
const SeamlessFilteredTabsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
SeamlessFilteredTabsProvider call(
|
||||
String? containerId,
|
||||
) {
|
||||
return SeamlessFilteredTabsProvider(
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SeamlessFilteredTabsProvider getProviderOverride(
|
||||
covariant SeamlessFilteredTabsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.containerId,
|
||||
);
|
||||
}
|
||||
|
||||
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'seamlessFilteredTabsProvider';
|
||||
}
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
class SeamlessFilteredTabsProvider extends AutoDisposeProvider<List<String>> {
|
||||
/// See also [seamlessFilteredTabs].
|
||||
SeamlessFilteredTabsProvider(
|
||||
String? containerId,
|
||||
) : this._internal(
|
||||
(ref) => seamlessFilteredTabs(
|
||||
ref as SeamlessFilteredTabsRef,
|
||||
containerId,
|
||||
),
|
||||
from: seamlessFilteredTabsProvider,
|
||||
name: r'seamlessFilteredTabsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$seamlessFilteredTabsHash,
|
||||
dependencies: SeamlessFilteredTabsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SeamlessFilteredTabsFamily._allTransitiveDependencies,
|
||||
containerId: containerId,
|
||||
);
|
||||
|
||||
SeamlessFilteredTabsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.containerId,
|
||||
}) : super.internal();
|
||||
|
||||
final String? containerId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
List<String> Function(SeamlessFilteredTabsRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SeamlessFilteredTabsProvider._internal(
|
||||
(ref) => create(ref as SeamlessFilteredTabsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
containerId: containerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<List<String>> createElement() {
|
||||
return _SeamlessFilteredTabsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SeamlessFilteredTabsProvider &&
|
||||
other.containerId == containerId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SeamlessFilteredTabsRef on AutoDisposeProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
}
|
||||
|
||||
class _SeamlessFilteredTabsProviderElement
|
||||
extends AutoDisposeProviderElement<List<String>>
|
||||
with SeamlessFilteredTabsRef {
|
||||
_SeamlessFilteredTabsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get containerId =>
|
||||
(origin as SeamlessFilteredTabsProvider).containerId;
|
||||
}
|
||||
|
||||
String _$selectedBangTriggerHash() =>
|
||||
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385';
|
||||
|
||||
|
||||
+28
-43
@@ -6,7 +6,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/data/models/web_page_info.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/site_search.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
@@ -14,13 +13,11 @@ import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class WebPageDialog extends HookConsumerWidget {
|
||||
@@ -39,9 +36,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
),
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
|
||||
final availableBangsAsync = ref.watch(
|
||||
@@ -57,8 +52,9 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
final availableBangCount = availableBangsAsync.valueOrNull?.length;
|
||||
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
final urlTextController = useTextEditingController(text: url.toString());
|
||||
final urlTextFocusNode = useFocusNode();
|
||||
final addressTextController =
|
||||
useTextEditingController(text: url.toString());
|
||||
final addressTextFocusNode = useFocusNode();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -83,46 +79,35 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: TextFormField(
|
||||
controller: urlTextController,
|
||||
focusNode: urlTextFocusNode,
|
||||
controller: addressTextController,
|
||||
focusNode: addressTextFocusNode,
|
||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'URL',
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState?.validate() ?? false) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: null).notifier)
|
||||
.loadUrl(
|
||||
url: uri_parser.tryParseUrl(
|
||||
urlTextController.text,
|
||||
eagerParsing: true,
|
||||
)!,
|
||||
);
|
||||
|
||||
onDismiss?.call();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.send),
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Address',
|
||||
),
|
||||
validator: (value) {
|
||||
if (uri_parser.tryParseUrl(value, eagerParsing: true) !=
|
||||
null) {
|
||||
if (value != null && Uri.tryParse(value) != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'Invalid URL';
|
||||
return 'Invalid Address';
|
||||
},
|
||||
onTap: () {
|
||||
if (!urlTextFocusNode.hasFocus) {
|
||||
if (!addressTextFocusNode.hasFocus) {
|
||||
// Select all text when the field is tapped
|
||||
urlTextController.selection = TextSelection(
|
||||
addressTextController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: urlTextController.text.length,
|
||||
extentOffset: addressTextController.text.length,
|
||||
);
|
||||
}
|
||||
},
|
||||
onFieldSubmitted: (value) async {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: null).notifier)
|
||||
.loadUrl(url: Uri.tryParse(value)!);
|
||||
|
||||
onDismiss?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -148,12 +133,12 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
loading: () => SiteSearch(
|
||||
domain: url.host,
|
||||
availableBangs: [
|
||||
BangData(
|
||||
websiteName: 'websiteName',
|
||||
domain: 'domain',
|
||||
trigger: 'trigger',
|
||||
urlTemplate: 'urlTemplate',
|
||||
),
|
||||
// BangData(
|
||||
// websiteName: 'websiteName',
|
||||
// domain: 'domain',
|
||||
// trigger: 'trigger',
|
||||
// urlTemplate: 'urlTemplate',
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -181,7 +166,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.tabPlus),
|
||||
title: const Text('Open in new tab'),
|
||||
title: const Text('Clone tab'),
|
||||
onTap: () async {
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(url: url);
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
@@ -30,13 +28,10 @@ import 'package:lensai/features/geckoview/features/find_in_page/presentation/wid
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/data/services/session.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:speech_to_text_google_dialog/speech_to_text_google_dialog.dart';
|
||||
|
||||
class BrowserScreen extends HookConsumerWidget {
|
||||
const BrowserScreen({super.key});
|
||||
@@ -54,26 +49,6 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||
final displayedOverlayDialog = ref.watch(overlayDialogControllerProvider);
|
||||
|
||||
final showEarlyAccessFeatures = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.showEarlyAccessFeatures,
|
||||
),
|
||||
);
|
||||
|
||||
final quickAction = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).quickAction,
|
||||
),
|
||||
);
|
||||
|
||||
final quickActionVoice = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.quickActionVoiceInput,
|
||||
),
|
||||
);
|
||||
|
||||
final selectedTabId =
|
||||
ref.watch(selectedTabStateProvider.select((value) => value?.id));
|
||||
|
||||
@@ -97,26 +72,23 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider
|
||||
.select((value) => value.valueOrNull?.enableJavascript),
|
||||
settingsRepositoryProvider.select((value) => value.enableJavascript),
|
||||
(previous, next) async {
|
||||
if (next != null) {
|
||||
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
|
||||
}
|
||||
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
|
||||
},
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider
|
||||
.select((value) => value.valueOrNull?.kagiSession),
|
||||
(previous, next) async {
|
||||
if (next != null && next.isNotEmpty) {
|
||||
await ref
|
||||
.read(kagiSessionServiceProvider.notifier)
|
||||
.setKagiSession(next);
|
||||
}
|
||||
},
|
||||
);
|
||||
// ref.listen(
|
||||
// settingsRepositoryProvider
|
||||
// .select((value) => value.valueOrNull?.kagiSession),
|
||||
// (previous, next) async {
|
||||
// if (next != null && next.isNotEmpty) {
|
||||
// await ref
|
||||
// .read(kagiSessionServiceProvider.notifier)
|
||||
// .setKagiSession(next);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
|
||||
return PopScope(
|
||||
//We need this for BackButtonListener to work downstream
|
||||
@@ -217,78 +189,78 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
},
|
||||
icon: Icon(KagiTool.summarizer.icon),
|
||||
),
|
||||
if (showEarlyAccessFeatures)
|
||||
IconButton(
|
||||
color: (activeTool == KagiTool.assistant)
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(
|
||||
createTabStreamProvider.notifier,
|
||||
)
|
||||
.createTab(
|
||||
CreateTabSheet(
|
||||
preferredTool: KagiTool.assistant,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(KagiTool.assistant.icon),
|
||||
),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// IconButton(
|
||||
// color: (activeTool == KagiTool.assistant)
|
||||
// ? Theme.of(context).colorScheme.primary
|
||||
// : null,
|
||||
// onPressed: () {
|
||||
// ref
|
||||
// .read(
|
||||
// createTabStreamProvider.notifier,
|
||||
// )
|
||||
// .createTab(
|
||||
// CreateTabSheet(
|
||||
// preferredTool: KagiTool.assistant,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// icon: Icon(KagiTool.assistant.icon),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
if (selectedTabId != null) ReaderButton(),
|
||||
if (quickAction != null)
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
var tab = CreateTabSheet(
|
||||
preferredTool: quickAction,
|
||||
);
|
||||
// if (quickAction != null)
|
||||
// InkWell(
|
||||
// onTap: () async {
|
||||
// var tab = CreateTabSheet(
|
||||
// preferredTool: quickAction,
|
||||
// );
|
||||
|
||||
if (quickActionVoice) {
|
||||
final completer = Completer<String>();
|
||||
// if (quickActionVoice) {
|
||||
// final completer = Completer<String>();
|
||||
|
||||
final isServiceAvailable =
|
||||
await SpeechToTextGoogleDialog.getInstance()
|
||||
.showGoogleDialog(
|
||||
onTextReceived: (data) {
|
||||
completer.complete(data.toString());
|
||||
},
|
||||
// locale: "en-US",
|
||||
);
|
||||
// final isServiceAvailable =
|
||||
// await SpeechToTextGoogleDialog.getInstance()
|
||||
// .showGoogleDialog(
|
||||
// onTextReceived: (data) {
|
||||
// completer.complete(data.toString());
|
||||
// },
|
||||
// // locale: "en-US",
|
||||
// );
|
||||
|
||||
if (!isServiceAvailable) {
|
||||
if (context.mounted) {
|
||||
ui_helper.showErrorMessage(
|
||||
context,
|
||||
'Service is not available',
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (!isServiceAvailable) {
|
||||
// if (context.mounted) {
|
||||
// ui_helper.showErrorMessage(
|
||||
// context,
|
||||
// 'Service is not available',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
tab = CreateTabSheet(
|
||||
preferredTool: quickAction,
|
||||
content: await completer.future,
|
||||
);
|
||||
}
|
||||
// tab = CreateTabSheet(
|
||||
// preferredTool: quickAction,
|
||||
// content: await completer.future,
|
||||
// );
|
||||
// }
|
||||
|
||||
ref
|
||||
.read(createTabStreamProvider.notifier)
|
||||
.createTab(tab);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Icon(
|
||||
quickActionVoice ? Icons.mic : quickAction.icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
// ref
|
||||
// .read(createTabStreamProvider.notifier)
|
||||
// .createTab(tab);
|
||||
// },
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// vertical: 15.0,
|
||||
// horizontal: 8.0,
|
||||
// ),
|
||||
// child: Icon(
|
||||
// quickActionVoice ? Icons.mic : quickAction.icon,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
TabsActionButton(
|
||||
isActive: displayedSheet is ViewTabsSheet,
|
||||
onTap: () {
|
||||
@@ -394,8 +366,7 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(addonServiceProvider)
|
||||
await addonService
|
||||
.startAddonManagerActivity();
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.puzzleEdit),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
|
||||
class BrowserView extends StatefulHookConsumerWidget {
|
||||
final Duration screenshotPeriod;
|
||||
@@ -101,6 +102,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
webExtensionsStateProvider(WebExtensionActionType.page),
|
||||
(previous, next) {},
|
||||
);
|
||||
|
||||
ref.listenManual(
|
||||
cacheRepositoryProvider,
|
||||
(previous, next) {},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+8
-6
@@ -4,17 +4,19 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
|
||||
class LandingAction extends HookConsumerWidget {
|
||||
const LandingAction({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sessionTokenAvailable = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
|
||||
),
|
||||
);
|
||||
//TODO: REWORK!
|
||||
final sessionTokenAvailable = false;
|
||||
|
||||
// ref.watch(
|
||||
// settingsRepositoryProvider.select(
|
||||
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
|
||||
// ),
|
||||
// );
|
||||
|
||||
return Visibility(
|
||||
visible: !sessionTokenAvailable,
|
||||
|
||||
+20
-28
@@ -4,11 +4,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/assistant_tab.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/search_tab.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/summarize_tab.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/presentation/hooks/sync_page_tab.dart';
|
||||
|
||||
@@ -27,13 +24,6 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final showEarlyAccessFeatures = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.showEarlyAccessFeatures,
|
||||
),
|
||||
);
|
||||
|
||||
final sharedContent = useMemoized(
|
||||
() => (parameter.content != null)
|
||||
? SharedContent.parse(parameter.content!)
|
||||
@@ -45,20 +35,22 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
() =>
|
||||
parameter.preferredTool?.index ??
|
||||
switch (sharedContent) {
|
||||
SharedText(text: final text) =>
|
||||
(showEarlyAccessFeatures && text.length > 25)
|
||||
? KagiTool.assistant.index
|
||||
: KagiTool.search.index,
|
||||
SharedText(text: final text) => KagiTool.search.index
|
||||
// (showEarlyAccessFeatures && text.length > 25)
|
||||
// ? KagiTool.assistant.index
|
||||
// : KagiTool.search.index,
|
||||
,
|
||||
SharedUrl() => KagiTool.summarizer.index,
|
||||
null => showEarlyAccessFeatures
|
||||
? KagiTool.assistant.index
|
||||
: KagiTool.search.index,
|
||||
null => KagiTool.search.index
|
||||
// showEarlyAccessFeatures
|
||||
// ? KagiTool.assistant.index
|
||||
// : KagiTool.search.index,
|
||||
},
|
||||
[sharedContent],
|
||||
);
|
||||
|
||||
final tabController = useTabController(
|
||||
initialLength: KagiTool.values.length - (showEarlyAccessFeatures ? 0 : 1),
|
||||
initialLength: KagiTool.values.length - 1,
|
||||
initialIndex: initialIndex,
|
||||
);
|
||||
final pageController = usePageController(initialPage: tabController.index);
|
||||
@@ -95,11 +87,11 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
icon: Icon(KagiTool.summarizer.icon),
|
||||
text: 'Summarize',
|
||||
),
|
||||
if (showEarlyAccessFeatures)
|
||||
Tab(
|
||||
icon: Icon(KagiTool.assistant.icon),
|
||||
text: 'Assistant',
|
||||
),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// Tab(
|
||||
// icon: Icon(KagiTool.assistant.icon),
|
||||
// text: 'Assistant',
|
||||
// ),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
@@ -115,11 +107,11 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
sharedContent: sharedContent,
|
||||
onSubmit: onSubmit,
|
||||
),
|
||||
if (showEarlyAccessFeatures)
|
||||
AssistantTab(
|
||||
sharedContent: sharedContent,
|
||||
onSubmit: onSubmit,
|
||||
),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// AssistantTab(
|
||||
// sharedContent: sharedContent,
|
||||
// onSubmit: onSubmit,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
+296
-202
@@ -10,48 +10,65 @@ import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/tab_action.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||
import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:reorderable_grid/reorderable_grid.dart';
|
||||
|
||||
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
static const _headerSize = 104.0;
|
||||
|
||||
class _Tab extends HookConsumerWidget {
|
||||
final VoidCallback onClose;
|
||||
|
||||
_SliverHeaderDelagate({required this.onClose});
|
||||
const _Tab({required this.onClose});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchMode = useState(false);
|
||||
final searchTextFocus = useFocusNode();
|
||||
final searchTextController = useTextEditingController();
|
||||
|
||||
final hasSearchText = useListenableSelector(
|
||||
searchTextController,
|
||||
() => searchTextController.text.isNotEmpty,
|
||||
);
|
||||
|
||||
useListenableCallback(
|
||||
searchTextController,
|
||||
() async {
|
||||
await ref
|
||||
.read(tabSearchRepositoryProvider.notifier)
|
||||
.addQuery(searchTextController.text);
|
||||
},
|
||||
);
|
||||
|
||||
return Material(
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
//Fix layout issue https://github.com/flutter/flutter/issues/78748#issuecomment-1194680555
|
||||
return Align(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
//Fix layout issue https://github.com/flutter/flutter/issues/78748#issuecomment-1194680555
|
||||
child: Align(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8.0,
|
||||
left: 8.0,
|
||||
top: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!searchMode.value)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: Uri.https('kagi.com'));
|
||||
|
||||
onClose();
|
||||
icon: const Icon(Icons.search),
|
||||
label: const Text('Search'),
|
||||
onPressed: () {
|
||||
searchMode.value = true;
|
||||
searchTextFocus.requestFocus();
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('New Tab'),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
@@ -66,46 +83,92 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
label: const Text('Close All'),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final selectedContainer = ref.watch(
|
||||
selectedContainerDataProvider
|
||||
.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
return ContainerChips(
|
||||
selectedContainer: selectedContainer,
|
||||
onSelected: (container) {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
},
|
||||
onDeleted: (container) async {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.clearContainer();
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
TextField(
|
||||
controller: searchTextController,
|
||||
focusNode: searchTextFocus,
|
||||
// enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
decoration: InputDecoration(
|
||||
// border: InputBorder.none,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
hintText: 'Search inside tabs...',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!hasSearchText)
|
||||
SpeechToTextButton(
|
||||
onTextReceived: (data) {
|
||||
searchTextController.text = data.toString();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
searchTextController.clear();
|
||||
searchMode.value = false;
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0, top: 12),
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final selectedContainer = ref.watch(
|
||||
selectedContainerDataProvider
|
||||
.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
return ContainerChips(
|
||||
selectedContainer: selectedContainer,
|
||||
onSelected: (container) {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
},
|
||||
onDeleted: (container) async {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.clearContainer();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
static const headerSize = 124.0;
|
||||
|
||||
final VoidCallback onClose;
|
||||
|
||||
_SliverHeaderDelagate({required this.onClose});
|
||||
|
||||
@override
|
||||
double get minExtent => _headerSize;
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
return _Tab(onClose: onClose);
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => _headerSize;
|
||||
double get minExtent => headerSize;
|
||||
|
||||
@override
|
||||
double get maxExtent => headerSize;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
|
||||
@@ -143,165 +206,196 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return CustomScrollView(
|
||||
controller: sheetScrollController,
|
||||
slivers: [
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _SliverHeaderDelagate(onClose: onClose),
|
||||
),
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
final availableTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(container).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
final activeTab = ref.watch(selectedTabProvider);
|
||||
return Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
CustomScrollView(
|
||||
controller: sheetScrollController,
|
||||
slivers: [
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _SliverHeaderDelagate(onClose: onClose),
|
||||
),
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
|
||||
final itemHeight = useMemoized(
|
||||
() => _calculateItemHeight(
|
||||
screenWidth: MediaQuery.of(context).size.width,
|
||||
childAspectRatio: 0.75,
|
||||
horizontalPadding: 4.0,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
[MediaQuery.of(context).size.width],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final index =
|
||||
availableTabs.indexWhere((webView) => webView == activeTab);
|
||||
|
||||
if (index > -1) {
|
||||
final offset = (index ~/ 2) * itemHeight;
|
||||
|
||||
if (offset != sheetScrollController.offset) {
|
||||
unawaited(
|
||||
sheetScrollController.animateTo(
|
||||
offset,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
final filteredTabs = ref
|
||||
.watch(
|
||||
seamlessFilteredTabsProvider(container).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
.collection;
|
||||
|
||||
return null;
|
||||
},
|
||||
[availableTabs, activeTab],
|
||||
);
|
||||
final activeTab = ref.watch(selectedTabProvider);
|
||||
|
||||
final tabs = useMemoized(
|
||||
() => availableTabs
|
||||
.mapIndexed(
|
||||
(index, tabId) => ReorderableGridDelayedDragStartListener(
|
||||
key: ValueKey(tabId),
|
||||
index: index,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final tab = ref.watch(tabStateProvider(tabId));
|
||||
return (tab != null)
|
||||
? TabPreview(
|
||||
tab: tab,
|
||||
isActive: tabId == activeTab,
|
||||
onTap: () async {
|
||||
if (tabId != activeTab) {
|
||||
//Close first to avoid rebuilds
|
||||
onClose();
|
||||
await ref
|
||||
.read(
|
||||
tabRepositoryProvider.notifier,
|
||||
)
|
||||
.selectTab(tab.id);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onDoubleTap: () {
|
||||
ref
|
||||
.read(
|
||||
overlayDialogControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.show(
|
||||
TabActionDialog(
|
||||
initialTab: tab,
|
||||
onDismiss: ref
|
||||
final itemHeight = useMemoized(
|
||||
() => _calculateItemHeight(
|
||||
screenWidth: MediaQuery.of(context).size.width,
|
||||
childAspectRatio: 0.75,
|
||||
horizontalPadding: 4.0,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
[MediaQuery.of(context).size.width],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final index = filteredTabs
|
||||
.indexWhere((webView) => webView == activeTab);
|
||||
|
||||
if (index > -1) {
|
||||
final offset = (index ~/ 2) * itemHeight;
|
||||
|
||||
if (offset != sheetScrollController.offset) {
|
||||
unawaited(
|
||||
sheetScrollController.animateTo(
|
||||
offset,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[filteredTabs, activeTab],
|
||||
);
|
||||
|
||||
final tabs = useMemoized(
|
||||
() {
|
||||
return filteredTabs
|
||||
.mapIndexed(
|
||||
(index, tabId) =>
|
||||
ReorderableGridDelayedDragStartListener(
|
||||
key: ValueKey(tabId),
|
||||
index: index,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final tab = ref.watch(tabStateProvider(tabId));
|
||||
return (tab != null)
|
||||
? TabPreview(
|
||||
tab: tab,
|
||||
isActive: tabId == activeTab,
|
||||
onTap: () async {
|
||||
if (tabId != activeTab) {
|
||||
//Close first to avoid rebuilds
|
||||
onClose();
|
||||
await ref
|
||||
.read(
|
||||
overlayDialogControllerProvider
|
||||
tabRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.dismiss,
|
||||
),
|
||||
);
|
||||
},
|
||||
onDelete: () async {
|
||||
await ref
|
||||
.read(
|
||||
tabRepositoryProvider.notifier,
|
||||
)
|
||||
.closeTab(tab.id);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
.selectTab(tab.id);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
[
|
||||
EquatableCollection(filteredTabs, immutable: true),
|
||||
activeTab
|
||||
],
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
sliver: SliverReorderableGrid(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
//Sync values for itemHeight calculation _calculateItemHeight
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
[availableTabs, activeTab],
|
||||
);
|
||||
itemCount: tabs.length,
|
||||
itemBuilder: (context, index) => tabs[index],
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
sliver: SliverReorderableGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
//Sync values for itemHeight calculation _calculateItemHeight
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
itemCount: tabs.length,
|
||||
itemBuilder: (context, index) => tabs[index],
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
final tabId = filteredTabs[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
|
||||
final tabId = availableTabs[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
final String key;
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= filteredTabs.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex;
|
||||
key = await containerRepository.getOrderKeyAfterTab(
|
||||
filteredTabs[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
final String key;
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= availableTabs.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex - 1;
|
||||
key = await containerRepository.getOrderKeyAfterTab(
|
||||
availableTabs[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignOrderKey(tabId, key);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: _SliverHeaderDelagate.headerSize + 4,
|
||||
right: 4,
|
||||
),
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: Uri.https('kagi.com'));
|
||||
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignOrderKey(tabId, key);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
onClose();
|
||||
},
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -91,9 +91,11 @@ class TabPreview extends StatelessWidget {
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: RawImage(
|
||||
image: tab.thumbnail!.value,
|
||||
fit: BoxFit.fitWidth,
|
||||
child: RepaintBoundary(
|
||||
child: RawImage(
|
||||
image: tab.thumbnail!.value,
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user