refactoring
This commit is contained in:
@@ -1,24 +1,8 @@
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
|
||||
sealed class Sheet with FastEquatable {}
|
||||
|
||||
class CreateTabSheet extends Sheet {
|
||||
final String? content;
|
||||
final KagiTool? preferredTool;
|
||||
|
||||
bool get hasParameters => content != null || preferredTool != null;
|
||||
|
||||
CreateTabSheet({this.content, this.preferredTool});
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [content, preferredTool];
|
||||
}
|
||||
|
||||
class ViewTabsSheet extends Sheet {
|
||||
final class ViewTabsSheet extends Sheet {
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@@ -26,7 +10,7 @@ class ViewTabsSheet extends Sheet {
|
||||
List<Object?> get hashParameters => [];
|
||||
}
|
||||
|
||||
class TabQaChatSheet extends Sheet {
|
||||
final class TabQaChatSheet extends Sheet {
|
||||
final String chatId;
|
||||
|
||||
TabQaChatSheet({required this.chatId});
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/domain/entities/equatable_iterable.dart';
|
||||
import 'package:lensai/extensions/nullable.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/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/entities/tab_preview.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/entities/container_filter.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';
|
||||
|
||||
@@ -58,48 +57,6 @@ class SelectedBangData extends _$SelectedBangData {
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class LastUsedAssistantMode extends _$LastUsedAssistantMode {
|
||||
// ignore: document_ignores api decision
|
||||
// ignore: use_setters_to_change_properties
|
||||
void update(AssistantMode mode) {
|
||||
state = mode;
|
||||
}
|
||||
|
||||
@override
|
||||
AssistantMode build() {
|
||||
return AssistantMode.research;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ActiveResearchVariant extends _$ActiveResearchVariant {
|
||||
// ignore: document_ignores api decision
|
||||
// ignore: use_setters_to_change_properties
|
||||
void update(ResearchVariant mode) {
|
||||
state = mode;
|
||||
}
|
||||
|
||||
@override
|
||||
ResearchVariant build() {
|
||||
return ResearchVariant.expert;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ActiveChatModel extends _$ActiveChatModel {
|
||||
// ignore: document_ignores api decision
|
||||
// ignore: use_setters_to_change_properties
|
||||
void update(ChatModel model) {
|
||||
state = model;
|
||||
}
|
||||
|
||||
@override
|
||||
ChatModel build() {
|
||||
return ChatModel.gpt4o;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ShowFindInPage extends _$ShowFindInPage {
|
||||
// ignore: document_ignores api decision
|
||||
@@ -127,7 +84,10 @@ EquatableCollection<List<String>> availableTabIds(
|
||||
final tabList = ref.watch(tabListProvider);
|
||||
|
||||
return EquatableCollection(
|
||||
containerTabs?.where((tabId) => tabList.contains(tabId)).toList() ?? [],
|
||||
containerTabs
|
||||
?.where((tabId) => tabList.collection.contains(tabId))
|
||||
.toList() ??
|
||||
[],
|
||||
immutable: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'providers.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$availableTabIdsHash() => r'd8b55b012b38111c8d5e069987a55645d1a71b89';
|
||||
String _$availableTabIdsHash() => r'e5c5f25dea67f7739dfb1d0d9e4453079817a7bc';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -908,56 +908,6 @@ class _SelectedBangDataProviderElement
|
||||
String? get domain => (origin as SelectedBangDataProvider).domain;
|
||||
}
|
||||
|
||||
String _$lastUsedAssistantModeHash() =>
|
||||
r'1255a2754a3ea5ea058d110fd9743897fe103a69';
|
||||
|
||||
/// See also [LastUsedAssistantMode].
|
||||
@ProviderFor(LastUsedAssistantMode)
|
||||
final lastUsedAssistantModeProvider =
|
||||
NotifierProvider<LastUsedAssistantMode, AssistantMode>.internal(
|
||||
LastUsedAssistantMode.new,
|
||||
name: r'lastUsedAssistantModeProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$lastUsedAssistantModeHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$LastUsedAssistantMode = Notifier<AssistantMode>;
|
||||
String _$activeResearchVariantHash() =>
|
||||
r'8c65a3f5ead6cc4f9e2af85b607b7a49b0764ee3';
|
||||
|
||||
/// See also [ActiveResearchVariant].
|
||||
@ProviderFor(ActiveResearchVariant)
|
||||
final activeResearchVariantProvider =
|
||||
NotifierProvider<ActiveResearchVariant, ResearchVariant>.internal(
|
||||
ActiveResearchVariant.new,
|
||||
name: r'activeResearchVariantProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$activeResearchVariantHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ActiveResearchVariant = Notifier<ResearchVariant>;
|
||||
String _$activeChatModelHash() => r'675f406af8b4aa92699d59e37b5805a81d03a799';
|
||||
|
||||
/// See also [ActiveChatModel].
|
||||
@ProviderFor(ActiveChatModel)
|
||||
final activeChatModelProvider =
|
||||
NotifierProvider<ActiveChatModel, ChatModel>.internal(
|
||||
ActiveChatModel.new,
|
||||
name: r'activeChatModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$activeChatModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ActiveChatModel = Notifier<ChatModel>;
|
||||
String _$showFindInPageHash() => r'7ee5703f7d0c7e8a8dd6b0849d7b1e0a41fe24d9';
|
||||
|
||||
/// See also [ShowFindInPage].
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/data/models/received_intent_parameter.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/app_widget/domain/services/home_widget.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/share_intent/domain/services/sharing_intent.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'intent.g.dart';
|
||||
|
||||
final _contentParserTransformer =
|
||||
StreamTransformer<ReceivedIntentParameter, SharedContent>.fromHandlers(
|
||||
handleData: (parameter, sink) {
|
||||
final parsed = parameter.content.mapNotNull(
|
||||
(content) => SharedContent.parse(content),
|
||||
);
|
||||
|
||||
if (parsed != null) {
|
||||
sink.add(parsed);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@Riverpod()
|
||||
class IntentStream extends _$IntentStream {
|
||||
late StreamController<SharedContent> _streamController;
|
||||
|
||||
@override
|
||||
Stream<SharedContent> build() {
|
||||
_streamController = StreamController();
|
||||
ref.onDispose(() async {
|
||||
await _streamController.close();
|
||||
});
|
||||
|
||||
final sharingItentStream = ref.watch(sharingIntentStreamProvider);
|
||||
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
|
||||
|
||||
return MergeStream([
|
||||
sharingItentStream.transform(_contentParserTransformer),
|
||||
appWidgetLaunchStream.transform(_contentParserTransformer),
|
||||
_streamController.stream,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'intent.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$intentStreamHash() => r'e78268ec703cd0d1f08d41b9e10a487e6000b7da';
|
||||
|
||||
/// See also [IntentStream].
|
||||
@ProviderFor(IntentStream)
|
||||
final intentStreamProvider =
|
||||
AutoDisposeStreamNotifierProvider<IntentStream, SharedContent>.internal(
|
||||
IntentStream.new,
|
||||
name: r'intentStreamProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$intentStreamHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$IntentStream = AutoDisposeStreamNotifier<SharedContent>;
|
||||
// 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
|
||||
+2
-2
@@ -2,10 +2,10 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/user/data/models/general_settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'delete_browser_data.g.dart';
|
||||
part 'browser_data.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class DeleteBrowserDataService extends _$DeleteBrowserDataService {
|
||||
class BrowserDataService extends _$BrowserDataService {
|
||||
final _service = GeckoDeleteBrowserDataService();
|
||||
|
||||
Future<void> deleteData(Set<DeleteBrowsingDataType>? types) async {
|
||||
+11
-10
@@ -1,26 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'create_tab.dart';
|
||||
part of 'browser_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$createTabStreamHash() => r'2e802a10ecaf1862074f32b76e89c26997bdd7b2';
|
||||
String _$browserDataServiceHash() =>
|
||||
r'f56c60635505e06dbd5b05f1bcee9c25c4d0eaaf';
|
||||
|
||||
/// See also [CreateTabStream].
|
||||
@ProviderFor(CreateTabStream)
|
||||
final createTabStreamProvider =
|
||||
AutoDisposeStreamNotifierProvider<CreateTabStream, CreateTabSheet>.internal(
|
||||
CreateTabStream.new,
|
||||
name: r'createTabStreamProvider',
|
||||
/// See also [BrowserDataService].
|
||||
@ProviderFor(BrowserDataService)
|
||||
final browserDataServiceProvider =
|
||||
AutoDisposeNotifierProvider<BrowserDataService, void>.internal(
|
||||
BrowserDataService.new,
|
||||
name: r'browserDataServiceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$createTabStreamHash,
|
||||
: _$browserDataServiceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTabSheet>;
|
||||
typedef _$BrowserDataService = AutoDisposeNotifier<void>;
|
||||
// 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
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:lensai/data/models/received_parameter.dart';
|
||||
import 'package:lensai/features/app_widget/domain/services/home_widget.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/share_intent/domain/services/sharing_intent.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'create_tab.g.dart';
|
||||
|
||||
final _parameterToCreateTabSheetTransformer =
|
||||
StreamTransformer<ReceivedParameter, CreateTabSheet>.fromHandlers(
|
||||
handleData: (parameter, sink) {
|
||||
final createTab = CreateTabSheet(
|
||||
preferredTool: KagiTool.values.firstWhereOrNull(
|
||||
(tool) => tool.name == parameter.tool,
|
||||
),
|
||||
content: parameter.content,
|
||||
);
|
||||
|
||||
if (createTab.hasParameters) {
|
||||
sink.add(createTab);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@Riverpod()
|
||||
class CreateTabStream extends _$CreateTabStream {
|
||||
late StreamController<CreateTabSheet> _streamController;
|
||||
|
||||
@override
|
||||
Stream<CreateTabSheet> build() {
|
||||
_streamController = StreamController();
|
||||
ref.onDispose(() async {
|
||||
await _streamController.close();
|
||||
});
|
||||
|
||||
final sharingItentStream = ref.watch(sharingIntentStreamProvider);
|
||||
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
|
||||
|
||||
return MergeStream([
|
||||
sharingItentStream.transform(_parameterToCreateTabSheetTransformer),
|
||||
appWidgetLaunchStream.transform(_parameterToCreateTabSheetTransformer),
|
||||
_streamController.stream,
|
||||
]);
|
||||
}
|
||||
|
||||
void createTab(CreateTabSheet parameter) {
|
||||
if (!_streamController.isClosed) {
|
||||
_streamController.add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'delete_browser_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$deleteBrowserDataServiceHash() =>
|
||||
r'f5fdbac17c14693fceac913591750ca9285b496c';
|
||||
|
||||
/// See also [DeleteBrowserDataService].
|
||||
@ProviderFor(DeleteBrowserDataService)
|
||||
final deleteBrowserDataServiceProvider =
|
||||
AutoDisposeNotifierProvider<DeleteBrowserDataService, void>.internal(
|
||||
DeleteBrowserDataService.new,
|
||||
name: r'deleteBrowserDataServiceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$deleteBrowserDataServiceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$DeleteBrowserDataService = AutoDisposeNotifier<void>;
|
||||
// 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
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/domain/entities/equatable_iterable.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
// import 'package:collection/collection.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
// import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
// import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
// import 'package:lensai/extensions/nullable.dart';
|
||||
// import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
// import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
// import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
// import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
|
||||
// class TabActionDialog extends HookConsumerWidget {
|
||||
// final TabState initialTab;
|
||||
|
||||
// final void Function()? onDismiss;
|
||||
|
||||
// const TabActionDialog({
|
||||
// required this.initialTab,
|
||||
// this.onDismiss,
|
||||
// super.key,
|
||||
// });
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context, WidgetRef ref) {
|
||||
// final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
|
||||
// final tabContainerId = ref.watch(
|
||||
// tabContainerIdProvider(initialTab.id)
|
||||
// .select((value) => value.valueOrNull),
|
||||
// );
|
||||
|
||||
// final containers = ref
|
||||
// .watch(
|
||||
// containersWithCountProvider.select(
|
||||
// (value) => EquatableCollection(value.valueOrNull, immutable: true),
|
||||
// ),
|
||||
// )
|
||||
// .collection;
|
||||
|
||||
// final selectedContainer = containers
|
||||
// ?.firstWhereOrNull((container) => container.id == tabContainerId);
|
||||
|
||||
// final expansionController = useExpansionTileController();
|
||||
|
||||
// return Stack(
|
||||
// children: [
|
||||
// ModalBarrier(
|
||||
// color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
|
||||
// onDismiss: onDismiss,
|
||||
// ),
|
||||
// SimpleDialog(
|
||||
// titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
|
||||
// contentPadding: EdgeInsets.zero,
|
||||
// insetPadding: const EdgeInsets.symmetric(
|
||||
// horizontal: 20.0,
|
||||
// vertical: 24.0,
|
||||
// ),
|
||||
// title: ListTile(
|
||||
// leading: RawImage(
|
||||
// image: tabState.icon?.value,
|
||||
// width: 24,
|
||||
// height: 24,
|
||||
// ),
|
||||
// contentPadding: EdgeInsets.zero,
|
||||
// title: Text(tabState.title),
|
||||
// subtitle: Text(tabState.url.authority),
|
||||
// ),
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// //We need this to stretch the dialog, then padding from dialog is applied
|
||||
// width: double.maxFinite,
|
||||
// child: ExpansionTile(
|
||||
// controller: expansionController,
|
||||
// leading: selectedContainer?.color.mapNotNull(
|
||||
// (color) => CircleAvatar(backgroundColor: color),
|
||||
// ),
|
||||
// title: (selectedContainer != null)
|
||||
// ? Text(selectedContainer.name ?? 'New Container')
|
||||
// : const Text('Assign a Container'),
|
||||
// children: containers
|
||||
// ?.where((container) => container.id != tabContainerId)
|
||||
// .map(
|
||||
// (container) => ListTile(
|
||||
// leading:
|
||||
// CircleAvatar(backgroundColor: container.color),
|
||||
// title: Text(container.name ?? 'New Container'),
|
||||
// onTap: () async {
|
||||
// await ref
|
||||
// .read(tabDataRepositoryProvider.notifier)
|
||||
// .assignContainer(initialTab.id, container.id);
|
||||
|
||||
// expansionController.collapse();
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
// .toList() ??
|
||||
// [],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
+23
-102
@@ -3,7 +3,6 @@ import 'dart:ui' as ui;
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -14,22 +13,16 @@ import 'package:lensai/features/bangs/presentation/widgets/site_search.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/domain/repositories/chat_metadata.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
|
||||
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/entities/sheet.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/providers/engine_suggestions.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/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/auto_suggest_text_field.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 {
|
||||
@@ -43,14 +36,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
|
||||
|
||||
final availableBangsAsync = ref.watch(
|
||||
bangDataListProvider(
|
||||
filter: (
|
||||
domain: url.host,
|
||||
groups: null,
|
||||
categoryFilter: null,
|
||||
orderMostFrequentFirst: true,
|
||||
),
|
||||
),
|
||||
bangListProvider(domain: url.host, orderMostFrequentFirst: true),
|
||||
);
|
||||
|
||||
final availableBangCount = availableBangsAsync.valueOrNull?.length;
|
||||
@@ -75,74 +61,9 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
width: double.maxFinite,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final addressTextController = useTextEditingController(
|
||||
text: url.toString(),
|
||||
);
|
||||
final addressTextFocusNode = useFocusNode();
|
||||
|
||||
final suggestion = useState<String?>(null);
|
||||
|
||||
useListenableCallback(addressTextController, () async {
|
||||
if (addressTextController.text.isNotEmpty) {
|
||||
final result = await ref
|
||||
.read(engineSuggestionsProvider.notifier)
|
||||
.getAutocompleteSuggestion(
|
||||
addressTextController.text,
|
||||
);
|
||||
|
||||
suggestion.value = result;
|
||||
}
|
||||
});
|
||||
|
||||
return AutoSuggestTextField(
|
||||
controller: addressTextController,
|
||||
focusNode: addressTextFocusNode,
|
||||
suggestion: suggestion.value,
|
||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Address'),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
onTap: () {
|
||||
if (!addressTextFocusNode.hasFocus) {
|
||||
// Select all text when the field is tapped
|
||||
addressTextController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: addressTextController.text.length,
|
||||
);
|
||||
}
|
||||
},
|
||||
onSubmitted: (value) async {
|
||||
if (value.isNotEmpty) {
|
||||
var newUrl = uri_parser.tryParseUrl(
|
||||
value,
|
||||
eagerParsing: true,
|
||||
);
|
||||
|
||||
if (newUrl == null) {
|
||||
final defaultSearchBang = await ref.read(
|
||||
defaultSearchBangDataProvider.future,
|
||||
);
|
||||
|
||||
newUrl = defaultSearchBang?.getUrl(value);
|
||||
}
|
||||
|
||||
if (newUrl != null) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: null).notifier)
|
||||
.loadUrl(url: newUrl);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
child: AddressWithSuggestionsField(
|
||||
url: url,
|
||||
incognitoEnabled: incognitoEnabled,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -273,7 +194,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: Icon(KagiTool.assistant.icon),
|
||||
leading: const Icon(MdiIcons.brain),
|
||||
title: const Text('QA Chat'),
|
||||
onTap: () async {
|
||||
final selectedTabId =
|
||||
@@ -306,24 +227,24 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(KagiTool.summarizer.icon),
|
||||
title: const Text('Summarize'),
|
||||
onTap: () async {
|
||||
final summarizerUrl = uri_builder.summarizerUri(
|
||||
document: SharedUrl(url),
|
||||
mode: SummarizerMode.keyMoments,
|
||||
);
|
||||
// ListTile(
|
||||
// leading: Icon(MdiIcons.text),
|
||||
// title: const Text('Summarize'),
|
||||
// onTap: () async {
|
||||
// final summarizerUrl = uri_builder.summarizerUri(
|
||||
// document: SharedUrl(url),
|
||||
// mode: SummarizerMode.keyMoments,
|
||||
// );
|
||||
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: summarizerUrl);
|
||||
// await ref
|
||||
// .read(tabRepositoryProvider.notifier)
|
||||
// .addTab(url: summarizerUrl);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
// if (context.mounted) {
|
||||
// context.pop();
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+85
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/providers/engine_suggestions.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/auto_suggest_text_field.dart';
|
||||
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
class AddressWithSuggestionsField extends HookConsumerWidget {
|
||||
const AddressWithSuggestionsField({
|
||||
super.key,
|
||||
required this.url,
|
||||
required this.incognitoEnabled,
|
||||
});
|
||||
|
||||
final Uri url;
|
||||
final bool incognitoEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final addressTextController = useTextEditingController(
|
||||
text: url.toString(),
|
||||
);
|
||||
final addressTextFocusNode = useFocusNode();
|
||||
|
||||
final suggestion = useState<String?>(null);
|
||||
|
||||
useListenableCallback(addressTextController, () async {
|
||||
if (addressTextController.text.isNotEmpty) {
|
||||
final result = await ref
|
||||
.read(engineSuggestionsProvider.notifier)
|
||||
.getAutocompleteSuggestion(addressTextController.text);
|
||||
|
||||
suggestion.value = result;
|
||||
}
|
||||
});
|
||||
|
||||
return AutoSuggestTextField(
|
||||
controller: addressTextController,
|
||||
focusNode: addressTextFocusNode,
|
||||
suggestion: suggestion.value,
|
||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Address'),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
onTap: () {
|
||||
if (!addressTextFocusNode.hasFocus) {
|
||||
// Select all text when the field is tapped
|
||||
addressTextController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: addressTextController.text.length,
|
||||
);
|
||||
}
|
||||
},
|
||||
onSubmitted: (value) async {
|
||||
if (value.isNotEmpty) {
|
||||
var newUrl = uri_parser.tryParseUrl(value, eagerParsing: true);
|
||||
|
||||
if (newUrl == null) {
|
||||
final defaultSearchBang = await ref.read(
|
||||
defaultSearchBangDataProvider.future,
|
||||
);
|
||||
|
||||
newUrl = defaultSearchBang?.getUrl(value);
|
||||
}
|
||||
|
||||
if (newUrl != null) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: null).notifier)
|
||||
.loadUrl(url: newUrl);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:text_scroll/text_scroll.dart';
|
||||
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
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/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_session.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/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/find_in_page/presentation/controllers/find_in_page_visibility.dart';
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:lensai/presentation/hooks/menu_controller.dart';
|
||||
import 'package:lensai/presentation/icons/tor_icons.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
const BrowserBottomAppBar({
|
||||
required this.selectedTabId,
|
||||
required this.displayedSheet,
|
||||
});
|
||||
|
||||
final String? selectedTabId;
|
||||
final Sheet? displayedSheet;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final addonService = ref.watch(addonServiceProvider);
|
||||
|
||||
final tabMenuController = useMenuController();
|
||||
final trippleDotMenuController = useMenuController();
|
||||
|
||||
return BottomAppBar(
|
||||
height: AppBar().preferredSize.height,
|
||||
padding: EdgeInsets.zero,
|
||||
child: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 8.0,
|
||||
title:
|
||||
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
||||
? HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final tabState = ref.watch(tabStateProvider(selectedTabId));
|
||||
|
||||
return (tabState != null)
|
||||
? AppBarTitle(
|
||||
tab: tabState,
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
WebPageRoute(
|
||||
url: tabState.url.toString(),
|
||||
).location,
|
||||
extra: tabState,
|
||||
);
|
||||
},
|
||||
onDoubleTap: () async {
|
||||
final newUrl = await showDialog<Uri?>(
|
||||
context: context,
|
||||
builder:
|
||||
(context) =>
|
||||
EditUrlDialog(initialUrl: tabState.url),
|
||||
);
|
||||
|
||||
if (newUrl != null) {
|
||||
await ref
|
||||
.read(
|
||||
tabSessionProvider(tabId: null).notifier,
|
||||
)
|
||||
.loadUrl(url: newUrl);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
)
|
||||
: null,
|
||||
actions: [
|
||||
if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
||||
ReaderButton(),
|
||||
MenuAnchor(
|
||||
controller: tabMenuController,
|
||||
builder: (context, controller, child) {
|
||||
return child!;
|
||||
},
|
||||
menuChildren: [
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(selectedTabId!);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
child: const Text('Close Tab'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(const SearchRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.add),
|
||||
child: const Text('Add Tab'),
|
||||
),
|
||||
],
|
||||
child: TabsActionButton(
|
||||
isActive: displayedSheet is ViewTabsSheet,
|
||||
onTap: () {
|
||||
if (displayedSheet case ViewTabsSheet()) {
|
||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||
} else {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.show(ViewTabsSheet());
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (tabMenuController.isOpen) {
|
||||
tabMenuController.close();
|
||||
} else {
|
||||
tabMenuController.open();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
MenuAnchor(
|
||||
controller: trippleDotMenuController,
|
||||
builder: (context, controller, child) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Icon(Icons.more_vert),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
Consumer(
|
||||
builder: (context, childRef, child) {
|
||||
final pageExtensions = childRef.watch(
|
||||
webExtensionsStateProvider(
|
||||
WebExtensionActionType.page,
|
||||
).select((value) => value.values.toList()),
|
||||
);
|
||||
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
...pageExtensions.map(
|
||||
(extension) => IconButton(
|
||||
onPressed: () async {
|
||||
//Use parents .ref because after onPressed this consumer gets disposed already
|
||||
await addonService.invokeAddonAction(
|
||||
extension.extensionId,
|
||||
WebExtensionActionType.page,
|
||||
);
|
||||
},
|
||||
icon: ExtensionBadgeIcon(extension),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(AboutRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.info),
|
||||
child: const Text('About'),
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(SettingsRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.settings),
|
||||
child: const Text('Settings'),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final browserExtensions = ref.watch(
|
||||
webExtensionsStateProvider(
|
||||
WebExtensionActionType.browser,
|
||||
).select((value) => value.values.toList()),
|
||||
);
|
||||
|
||||
return SubmenuButton(
|
||||
menuChildren: [
|
||||
...browserExtensions.map(
|
||||
(extension) => MenuItemButton(
|
||||
onPressed: () async {
|
||||
//Use parents .ref because after onPressed this consumer gets disposed already
|
||||
await addonService.invokeAddonAction(
|
||||
extension.extensionId,
|
||||
WebExtensionActionType.browser,
|
||||
);
|
||||
},
|
||||
leadingIcon: ExtensionBadgeIcon(extension),
|
||||
child: Text(extension.title ?? ''),
|
||||
),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await addonService.startAddonManagerActivity();
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.puzzleEdit),
|
||||
child: const Text('Addon Manager'),
|
||||
),
|
||||
],
|
||||
leadingIcon: const Icon(MdiIcons.puzzle),
|
||||
child: const Text('Addons'),
|
||||
);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(TorProxyRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(TorIcons.onionAlt),
|
||||
child: const Text('Tor'),
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(BangCategoriesRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.exclamationThick),
|
||||
child: const Text('Bangs'),
|
||||
),
|
||||
const Divider(),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId));
|
||||
|
||||
if (tabState?.url case final Uri url) {
|
||||
await ui_helper.launchUrlFeedback(context, url);
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.open_in_browser),
|
||||
child: const Text('Launch External'),
|
||||
),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId));
|
||||
|
||||
if (tabState?.url case final Uri url) {
|
||||
await Share.shareUri(url);
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.share),
|
||||
child: const Text('Share'),
|
||||
),
|
||||
if (selectedTabId != null) const Divider(),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(findInPageVisibilityControllerProvider.notifier)
|
||||
.show();
|
||||
},
|
||||
leadingIcon: const Icon(Icons.search),
|
||||
child: const Text('Find in page'),
|
||||
),
|
||||
if (selectedTabId != null) const Divider(),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
|
||||
await controller.reload();
|
||||
trippleDotMenuController.close();
|
||||
},
|
||||
leadingIcon: const Icon(Icons.refresh),
|
||||
child: const Text('Reload'),
|
||||
),
|
||||
if (selectedTabId != null) const Divider(),
|
||||
if (selectedTabId != null)
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final history = ref.watch(
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((value) => value?.historyState),
|
||||
);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child:
|
||||
(history?.canGoBack == true)
|
||||
? IconButton(
|
||||
onPressed: () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(
|
||||
tabId: selectedTabId,
|
||||
).notifier,
|
||||
);
|
||||
|
||||
await controller.goBack();
|
||||
trippleDotMenuController.close();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(selectedTabId!);
|
||||
trippleDotMenuController.close();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48, child: VerticalDivider()),
|
||||
Expanded(
|
||||
child: IconButton(
|
||||
onPressed:
|
||||
(history?.canGoForward == true)
|
||||
? () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(
|
||||
tabId: selectedTabId,
|
||||
).notifier,
|
||||
);
|
||||
|
||||
await controller.goForward();
|
||||
trippleDotMenuController.close();
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(UserAuthRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.info),
|
||||
child: const Text('Auth'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -6,10 +6,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/logger.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_session.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/geckoview/features/browser/domain/services/delete_browser_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/browser_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
|
||||
@@ -87,7 +88,7 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
.fetch()
|
||||
.then((settings) {
|
||||
ref
|
||||
.read(deleteBrowserDataServiceProvider.notifier)
|
||||
.read(browserDataServiceProvider.notifier)
|
||||
.deleteData(settings.deleteBrowsingDataOnQuit);
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/web_extension_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/states/web_extension.dart';
|
||||
|
||||
class ExtensionBadgeIcon extends StatelessWidget {
|
||||
final WebExtensionState state;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
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';
|
||||
|
||||
class LandingAction extends HookConsumerWidget {
|
||||
const LandingAction({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
//TODO: REWORK!
|
||||
final sessionTokenAvailable = false;
|
||||
|
||||
// ref.watch(
|
||||
// generalSettingsRepositoryProvider.select(
|
||||
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
|
||||
// ),
|
||||
// );
|
||||
|
||||
return Visibility(
|
||||
visible: !sessionTokenAvailable,
|
||||
child: ErrorContainer(
|
||||
content: Markdown(
|
||||
shrinkWrap: true,
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href == 'settings') {
|
||||
await context.push(SettingsRoute().location);
|
||||
}
|
||||
},
|
||||
data:
|
||||
'## No Kagi Session Provided\n'
|
||||
'Please navigate to [Settings](settings) and enter your Kagi Session Token.\n'
|
||||
"You must provide a valid session in order to use Kagi's features.",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/domain/services/app_initialization.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/landing/action.dart';
|
||||
import 'package:lensai/presentation/hooks/cached_future.dart';
|
||||
|
||||
class LandingContent extends HookConsumerWidget {
|
||||
const LandingContent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
final descriptionAsset = useCachedFuture(
|
||||
() async => rootBundle.loadString('assets/landing/description.md'),
|
||||
);
|
||||
final changelogAsset = useCachedFuture(
|
||||
() async => rootBundle.loadString('assets/landing/changelog.md'),
|
||||
);
|
||||
|
||||
return FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Lensai',
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
'The Privacy-Focused & AI-Powered Research Browser with Kagi integration',
|
||||
style: textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Image.asset('assets/icon/icon.png'),
|
||||
),
|
||||
const LandingAction(),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final errors =
|
||||
ref
|
||||
.watch(
|
||||
appInitializationServiceProvider.select(
|
||||
(result) => EquatableCollection(
|
||||
result.valueOrNull?.errors,
|
||||
immutable: false,
|
||||
),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
if (errors == null || errors.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...errors.map((error) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: ErrorContainer(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Error during App Initialization!',
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
Text(
|
||||
error.message,
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
if (error.details != null)
|
||||
Text(error.details.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(
|
||||
appInitializationServiceProvider.notifier,
|
||||
)
|
||||
.reinitialize();
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
label: const Text('Restart App'),
|
||||
icon: const Icon(Icons.refresh_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Markdown(
|
||||
data: descriptionAsset.data ?? '',
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href != null) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: Uri.parse(href));
|
||||
}
|
||||
},
|
||||
selectable: true,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
),
|
||||
Text(
|
||||
'Changelog',
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
MarkdownBody(data: changelogAsset.data ?? '', selectable: true),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
import 'package:expandable_page_view/expandable_page_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/extensions/nullable.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/summarize_tab.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/presentation/hooks/sync_page_tab.dart';
|
||||
|
||||
typedef OnSubmitUri = void Function(Uri url);
|
||||
|
||||
class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
final CreateTabSheet parameter;
|
||||
final OnSubmitUri onSubmit;
|
||||
final void Function(KagiTool tool)? onActiveToolChanged;
|
||||
|
||||
const CreateTabSheetWidget({
|
||||
required this.parameter,
|
||||
required this.onSubmit,
|
||||
this.onActiveToolChanged,
|
||||
super.key,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sharedContent = useMemoized(
|
||||
() => parameter.content.mapNotNull(
|
||||
(content) => SharedContent.parse(content),
|
||||
),
|
||||
[parameter],
|
||||
);
|
||||
|
||||
final initialIndex = useMemoized(
|
||||
() =>
|
||||
parameter.preferredTool?.index ??
|
||||
switch (sharedContent) {
|
||||
SharedText(text: final _) => KagiTool.search.index,
|
||||
// (showEarlyAccessFeatures && text.length > 25)
|
||||
// ? KagiTool.assistant.index
|
||||
// : KagiTool.search.index,
|
||||
SharedUrl() => KagiTool.summarizer.index,
|
||||
null => KagiTool.search.index,
|
||||
// showEarlyAccessFeatures
|
||||
// ? KagiTool.assistant.index
|
||||
// : KagiTool.search.index,
|
||||
},
|
||||
[sharedContent],
|
||||
);
|
||||
|
||||
final tabController = useTabController(
|
||||
initialLength: KagiTool.values.length - 1,
|
||||
initialIndex: initialIndex,
|
||||
);
|
||||
final pageController = usePageController(initialPage: tabController.index);
|
||||
|
||||
useSyncPageWithTab(
|
||||
tabController,
|
||||
pageController,
|
||||
onIndexChanged: (index) {
|
||||
onActiveToolChanged?.call(KagiTool.values[index]);
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
onActiveToolChanged?.call(KagiTool.values[initialIndex]);
|
||||
});
|
||||
return null;
|
||||
}, [initialIndex]);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TabBar(
|
||||
controller: tabController,
|
||||
tabs: [
|
||||
// Tab(
|
||||
// icon: Icon(KagiTool.search.icon),
|
||||
// text: 'Search',
|
||||
// ),
|
||||
Tab(icon: Icon(KagiTool.summarizer.icon), text: 'Summarize'),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// Tab(
|
||||
// icon: Icon(KagiTool.assistant.icon),
|
||||
// text: 'Assistant',
|
||||
// ),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
|
||||
child: ExpandablePageView(
|
||||
controller: pageController,
|
||||
children: [
|
||||
// SearchTab(
|
||||
// sharedContent: sharedContent,
|
||||
// onSubmit: onSubmit,
|
||||
// ),
|
||||
SummarizeTab(sharedContent: sharedContent, onSubmit: onSubmit),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// AssistantTab(
|
||||
// sharedContent: sharedContent,
|
||||
// onSubmit: onSubmit,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+8
-55
@@ -10,8 +10,6 @@ import 'package:lensai/core/providers/global_drop.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/data/models/drag_data.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
|
||||
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/widgets/draggable_scrollable_header.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||
@@ -25,60 +23,12 @@ import 'package:lensai/features/tor/presentation/controllers/start_tor_proxy.dar
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
||||
|
||||
class _TabDraggable extends HookConsumerWidget {
|
||||
final String tabId;
|
||||
final String? activeTabId;
|
||||
|
||||
final void Function() onClose;
|
||||
|
||||
_TabDraggable({
|
||||
required this.tabId,
|
||||
required this.activeTabId,
|
||||
required this.onClose,
|
||||
}) : super(key: ValueKey(tabId));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tab = ref.watch(tabStateProvider(tabId));
|
||||
|
||||
if (tab == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return TabPreview(
|
||||
tab: tab,
|
||||
isActive: tabId == activeTabId,
|
||||
onTap: () async {
|
||||
if (tabId != activeTabId) {
|
||||
//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.read(overlayDialogControllerProvider.notifier).dismiss,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
onDelete: () async {
|
||||
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tab extends HookConsumerWidget {
|
||||
class _TabSheetHeader extends HookConsumerWidget {
|
||||
static const headerSize = 124.0;
|
||||
|
||||
final VoidCallback onClose;
|
||||
|
||||
const _Tab({required this.onClose});
|
||||
const _TabSheetHeader({required this.onClose});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -245,7 +195,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
children: [
|
||||
DraggableScrollableHeader(
|
||||
controller: draggableScrollableController,
|
||||
child: _Tab(onClose: onClose),
|
||||
child: _TabSheetHeader(onClose: onClose),
|
||||
),
|
||||
Expanded(
|
||||
child: HookConsumer(
|
||||
@@ -304,7 +254,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
key: Key(tabId),
|
||||
data: TabDragData(tabId),
|
||||
child: Consumer(
|
||||
child: _TabDraggable(
|
||||
child: TabPreviewDraggable(
|
||||
tabId: tabId,
|
||||
activeTabId: activeTab,
|
||||
onClose: onClose,
|
||||
@@ -420,7 +370,10 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: _Tab.headerSize + 4, right: 4),
|
||||
padding: const EdgeInsets.only(
|
||||
top: _TabSheetHeader.headerSize + 4,
|
||||
right: 4,
|
||||
),
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: () async {
|
||||
await context.push(const SearchRoute().location);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
|
||||
class TabPreview extends StatelessWidget {
|
||||
final TabState tab;
|
||||
@@ -99,3 +102,51 @@ class TabPreview extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TabPreviewDraggable extends HookConsumerWidget {
|
||||
final String tabId;
|
||||
final String? activeTabId;
|
||||
|
||||
final void Function() onClose;
|
||||
|
||||
TabPreviewDraggable({
|
||||
required this.tabId,
|
||||
required this.activeTabId,
|
||||
required this.onClose,
|
||||
}) : super(key: ValueKey(tabId));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tab = ref.watch(tabStateProvider(tabId));
|
||||
|
||||
if (tab == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return TabPreview(
|
||||
tab: tab,
|
||||
isActive: tabId == activeTabId,
|
||||
onTap: () async {
|
||||
if (tabId != activeTabId) {
|
||||
//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.read(overlayDialogControllerProvider.notifier).dismiss,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
onDelete: () async {
|
||||
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -18,7 +18,9 @@ class TabsActionButton extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final tabCount = ref.watch(tabListProvider.select((tabs) => tabs.length));
|
||||
final tabCount = ref.watch(
|
||||
tabListProvider.select((tabs) => tabs.collection.length),
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
|
||||
Reference in New Issue
Block a user