bang and search
This commit is contained in:
@@ -9,13 +9,16 @@ part 'bang.g.dart';
|
|||||||
|
|
||||||
enum BangGroup {
|
enum BangGroup {
|
||||||
general(
|
general(
|
||||||
'https://raw.githubusercontent.com/kagisearch/bangs/main/data/bangs.json',
|
'https://raw.githubusercontent.com/FaFre/bangs/main/data/bangs.json',
|
||||||
),
|
),
|
||||||
assistant(
|
assistant(
|
||||||
'https://raw.githubusercontent.com/kagisearch/bangs/main/data/assistant_bangs.json',
|
'https://raw.githubusercontent.com/FaFre/bangs/main/data/assistant_bangs.json',
|
||||||
),
|
),
|
||||||
kagi(
|
kagi(
|
||||||
'https://raw.githubusercontent.com/kagisearch/bangs/main/data/kagi_bangs.json',
|
'https://raw.githubusercontent.com/FaFre/bangs/main/data/kagi_bangs.json',
|
||||||
|
),
|
||||||
|
custom(
|
||||||
|
'https://raw.githubusercontent.com/FaFre/bangs/refs/heads/custom/data/custom.json',
|
||||||
);
|
);
|
||||||
|
|
||||||
final String url;
|
final String url;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Stream<BangData?> bangData(Ref ref, String? trigger) {
|
|||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
Stream<BangData?> defaultSearchBangData(Ref ref) {
|
Stream<BangData?> defaultSearchBangData(Ref ref) {
|
||||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||||
return repository.watchBang('kagi');
|
return repository.watchBang('lai');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class _BangDataProviderElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$defaultSearchBangDataHash() =>
|
String _$defaultSearchBangDataHash() =>
|
||||||
r'b7547084b261ccb1dc02a6c7230fe57f6902dd58';
|
r'c93026fe7b1650fe8a39bb2a8850af3ee416c306';
|
||||||
|
|
||||||
/// See also [defaultSearchBangData].
|
/// See also [defaultSearchBangData].
|
||||||
@ProviderFor(defaultSearchBangData)
|
@ProviderFor(defaultSearchBangData)
|
||||||
|
|||||||
@@ -39,10 +39,7 @@ class SiteSearch extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final selectedBang = ref.watch(
|
final selectedBang = ref.watch(selectedBangDataProvider(domain: domain));
|
||||||
selectedBangDataProvider(domain: domain)
|
|
||||||
.select((value) => value.valueOrNull),
|
|
||||||
);
|
|
||||||
|
|
||||||
final activeBang = selectedBang ?? availableBangs.firstOrNull;
|
final activeBang = selectedBang ?? availableBangs.firstOrNull;
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,25 @@ class SelectedBangTrigger extends _$SelectedBangTrigger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
Stream<BangData?> selectedBangData(Ref ref, {String? domain}) {
|
class SelectedBangData extends _$SelectedBangData {
|
||||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
@override
|
||||||
final selectedBangTrigger =
|
BangData? build({String? domain}) {
|
||||||
ref.watch(selectedBangTriggerProvider(domain: domain));
|
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||||
return repository.watchBang(selectedBangTrigger);
|
final selectedBangTrigger =
|
||||||
|
ref.watch(selectedBangTriggerProvider(domain: domain));
|
||||||
|
|
||||||
|
final subscription = repository.watchBang(selectedBangTrigger).listen(
|
||||||
|
(value) {
|
||||||
|
state = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.onDispose(() {
|
||||||
|
unawaited(subscription.cancel());
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'providers.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$selectedBangDataHash() => r'd43e679c40d35c4f4c692ee286dc21789d7287dd';
|
String _$availableTabIdsHash() => r'd8b55b012b38111c8d5e069987a55645d1a71b89';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@@ -29,137 +29,6 @@ class _SystemHash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [selectedBangData].
|
|
||||||
@ProviderFor(selectedBangData)
|
|
||||||
const selectedBangDataProvider = SelectedBangDataFamily();
|
|
||||||
|
|
||||||
/// See also [selectedBangData].
|
|
||||||
class SelectedBangDataFamily extends Family<AsyncValue<BangData?>> {
|
|
||||||
/// See also [selectedBangData].
|
|
||||||
const SelectedBangDataFamily();
|
|
||||||
|
|
||||||
/// See also [selectedBangData].
|
|
||||||
SelectedBangDataProvider call({
|
|
||||||
String? domain,
|
|
||||||
}) {
|
|
||||||
return SelectedBangDataProvider(
|
|
||||||
domain: domain,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
SelectedBangDataProvider getProviderOverride(
|
|
||||||
covariant SelectedBangDataProvider provider,
|
|
||||||
) {
|
|
||||||
return call(
|
|
||||||
domain: provider.domain,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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'selectedBangDataProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [selectedBangData].
|
|
||||||
class SelectedBangDataProvider extends AutoDisposeStreamProvider<BangData?> {
|
|
||||||
/// See also [selectedBangData].
|
|
||||||
SelectedBangDataProvider({
|
|
||||||
String? domain,
|
|
||||||
}) : this._internal(
|
|
||||||
(ref) => selectedBangData(
|
|
||||||
ref as SelectedBangDataRef,
|
|
||||||
domain: domain,
|
|
||||||
),
|
|
||||||
from: selectedBangDataProvider,
|
|
||||||
name: r'selectedBangDataProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$selectedBangDataHash,
|
|
||||||
dependencies: SelectedBangDataFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
SelectedBangDataFamily._allTransitiveDependencies,
|
|
||||||
domain: domain,
|
|
||||||
);
|
|
||||||
|
|
||||||
SelectedBangDataProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.domain,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final String? domain;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(
|
|
||||||
Stream<BangData?> Function(SelectedBangDataRef provider) create,
|
|
||||||
) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
override: SelectedBangDataProvider._internal(
|
|
||||||
(ref) => create(ref as SelectedBangDataRef),
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
domain: domain,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeStreamProviderElement<BangData?> createElement() {
|
|
||||||
return _SelectedBangDataProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is SelectedBangDataProvider && other.domain == domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, domain.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
mixin SelectedBangDataRef on AutoDisposeStreamProviderRef<BangData?> {
|
|
||||||
/// The parameter `domain` of this provider.
|
|
||||||
String? get domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SelectedBangDataProviderElement
|
|
||||||
extends AutoDisposeStreamProviderElement<BangData?>
|
|
||||||
with SelectedBangDataRef {
|
|
||||||
_SelectedBangDataProviderElement(super.provider);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get domain => (origin as SelectedBangDataProvider).domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _$availableTabIdsHash() => r'd8b55b012b38111c8d5e069987a55645d1a71b89';
|
|
||||||
|
|
||||||
/// See also [availableTabIds].
|
/// See also [availableTabIds].
|
||||||
@ProviderFor(availableTabIds)
|
@ProviderFor(availableTabIds)
|
||||||
const availableTabIdsProvider = AvailableTabIdsFamily();
|
const availableTabIdsProvider = AvailableTabIdsFamily();
|
||||||
@@ -893,6 +762,152 @@ class _SelectedBangTriggerProviderElement
|
|||||||
String? get domain => (origin as SelectedBangTriggerProvider).domain;
|
String? get domain => (origin as SelectedBangTriggerProvider).domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _$selectedBangDataHash() => r'28c394e5989e108870ca5733e916b80b3cf8972c';
|
||||||
|
|
||||||
|
abstract class _$SelectedBangData
|
||||||
|
extends BuildlessAutoDisposeNotifier<BangData?> {
|
||||||
|
late final String? domain;
|
||||||
|
|
||||||
|
BangData? build({
|
||||||
|
String? domain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [SelectedBangData].
|
||||||
|
@ProviderFor(SelectedBangData)
|
||||||
|
const selectedBangDataProvider = SelectedBangDataFamily();
|
||||||
|
|
||||||
|
/// See also [SelectedBangData].
|
||||||
|
class SelectedBangDataFamily extends Family<BangData?> {
|
||||||
|
/// See also [SelectedBangData].
|
||||||
|
const SelectedBangDataFamily();
|
||||||
|
|
||||||
|
/// See also [SelectedBangData].
|
||||||
|
SelectedBangDataProvider call({
|
||||||
|
String? domain,
|
||||||
|
}) {
|
||||||
|
return SelectedBangDataProvider(
|
||||||
|
domain: domain,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SelectedBangDataProvider getProviderOverride(
|
||||||
|
covariant SelectedBangDataProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
domain: provider.domain,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'selectedBangDataProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [SelectedBangData].
|
||||||
|
class SelectedBangDataProvider
|
||||||
|
extends AutoDisposeNotifierProviderImpl<SelectedBangData, BangData?> {
|
||||||
|
/// See also [SelectedBangData].
|
||||||
|
SelectedBangDataProvider({
|
||||||
|
String? domain,
|
||||||
|
}) : this._internal(
|
||||||
|
() => SelectedBangData()..domain = domain,
|
||||||
|
from: selectedBangDataProvider,
|
||||||
|
name: r'selectedBangDataProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$selectedBangDataHash,
|
||||||
|
dependencies: SelectedBangDataFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
SelectedBangDataFamily._allTransitiveDependencies,
|
||||||
|
domain: domain,
|
||||||
|
);
|
||||||
|
|
||||||
|
SelectedBangDataProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.domain,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String? domain;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BangData? runNotifierBuild(
|
||||||
|
covariant SelectedBangData notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(
|
||||||
|
domain: domain,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(SelectedBangData Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: SelectedBangDataProvider._internal(
|
||||||
|
() => create()..domain = domain,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
domain: domain,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeNotifierProviderElement<SelectedBangData, BangData?>
|
||||||
|
createElement() {
|
||||||
|
return _SelectedBangDataProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is SelectedBangDataProvider && other.domain == domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, domain.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin SelectedBangDataRef on AutoDisposeNotifierProviderRef<BangData?> {
|
||||||
|
/// The parameter `domain` of this provider.
|
||||||
|
String? get domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectedBangDataProviderElement
|
||||||
|
extends AutoDisposeNotifierProviderElement<SelectedBangData, BangData?>
|
||||||
|
with SelectedBangDataRef {
|
||||||
|
_SelectedBangDataProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get domain => (origin as SelectedBangDataProvider).domain;
|
||||||
|
}
|
||||||
|
|
||||||
String _$lastUsedAssistantModeHash() =>
|
String _$lastUsedAssistantModeHash() =>
|
||||||
r'1255a2754a3ea5ea058d110fd9743897fe103a69';
|
r'1255a2754a3ea5ea058d110fd9743897fe103a69';
|
||||||
|
|
||||||
|
|||||||
+70
-39
@@ -19,13 +19,17 @@ 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/tab_state.dart';
|
||||||
import 'package:lensai/features/geckoview/domain/repositories/tab.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/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/data/entities/modes.dart';
|
||||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
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/share_intent/domain/entities/shared_content.dart';
|
||||||
import 'package:lensai/features/user/domain/providers.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/failure_widget.dart';
|
||||||
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
||||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
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';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
class WebPageDialog extends HookConsumerWidget {
|
class WebPageDialog extends HookConsumerWidget {
|
||||||
@@ -52,12 +56,8 @@ class WebPageDialog extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final availableBangCount = availableBangsAsync.valueOrNull?.length;
|
|
||||||
|
|
||||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
final availableBangCount = availableBangsAsync.valueOrNull?.length;
|
||||||
final addressTextController =
|
|
||||||
useTextEditingController(text: url.toString());
|
|
||||||
final addressTextFocusNode = useFocusNode();
|
|
||||||
|
|
||||||
return MediaQuery.removeViewInsets(
|
return MediaQuery.removeViewInsets(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -77,42 +77,73 @@ class WebPageDialog extends HookConsumerWidget {
|
|||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Form(
|
child: HookConsumer(
|
||||||
key: formKey,
|
builder: (context, ref, child) {
|
||||||
child: TextFormField(
|
final addressTextController =
|
||||||
controller: addressTextController,
|
useTextEditingController(text: url.toString());
|
||||||
focusNode: addressTextFocusNode,
|
final addressTextFocusNode = useFocusNode();
|
||||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Address',
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value != null && Uri.tryParse(value) != null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Invalid Address';
|
final suggestion = useState<String?>(null);
|
||||||
},
|
|
||||||
onTap: () {
|
|
||||||
if (!addressTextFocusNode.hasFocus) {
|
|
||||||
// Select all text when the field is tapped
|
|
||||||
addressTextController.selection = TextSelection(
|
|
||||||
baseOffset: 0,
|
|
||||||
extentOffset: addressTextController.text.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFieldSubmitted: (value) async {
|
|
||||||
await ref
|
|
||||||
.read(tabSessionProvider(tabId: null).notifier)
|
|
||||||
.loadUrl(url: Uri.tryParse(value)!);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
useListenableCallback(
|
||||||
context.pop();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ 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/providers/web_extensions_state.dart';
|
||||||
import 'package:lensai/features/geckoview/domain/repositories/tab.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/domain/entities/sheet.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/domain/services/create_tab.dart';
|
|
||||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart';
|
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_view.dart';
|
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_view.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.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/extension_badge_icon.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
|
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart';
|
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart';
|
||||||
@@ -125,57 +125,74 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
extra: tabState,
|
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();
|
: const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: HookBuilder(
|
: HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final activeTool = useListenableSelector(
|
// final activeTool = useListenableSelector(
|
||||||
activeKagiTool,
|
// activeKagiTool,
|
||||||
() {
|
// () {
|
||||||
if (displayedSheet is CreateTabSheetWidget) {
|
// if (displayedSheet is CreateTabSheetWidget) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return activeKagiTool.value;
|
// return activeKagiTool.value;
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
// IconButton(
|
||||||
color: (activeTool == KagiTool.search)
|
// color: (activeTool == KagiTool.search)
|
||||||
? Theme.of(context).colorScheme.primary
|
// ? Theme.of(context).colorScheme.primary
|
||||||
: null,
|
// : null,
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
// ref
|
// // ref
|
||||||
// .read(createTabStreamProvider.notifier)
|
// // .read(createTabStreamProvider.notifier)
|
||||||
// .createTab(
|
// // .createTab(
|
||||||
// CreateTabSheet(
|
// // CreateTabSheet(
|
||||||
// preferredTool: KagiTool.search,
|
// // preferredTool: KagiTool.search,
|
||||||
// ),
|
// // ),
|
||||||
// );
|
// // );
|
||||||
await context
|
// await context
|
||||||
.push(const SearchRoute().location);
|
// .push(const SearchRoute().location);
|
||||||
},
|
// },
|
||||||
icon: Icon(KagiTool.search.icon),
|
// icon: Icon(KagiTool.search.icon),
|
||||||
),
|
// ),
|
||||||
IconButton(
|
// IconButton(
|
||||||
color: (activeTool == KagiTool.summarizer)
|
// color: (activeTool == KagiTool.summarizer)
|
||||||
? Theme.of(context).colorScheme.primary
|
// ? Theme.of(context).colorScheme.primary
|
||||||
: null,
|
// : null,
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
ref
|
// ref
|
||||||
.read(createTabStreamProvider.notifier)
|
// .read(createTabStreamProvider.notifier)
|
||||||
.createTab(
|
// .createTab(
|
||||||
CreateTabSheet(
|
// CreateTabSheet(
|
||||||
preferredTool: KagiTool.summarizer,
|
// preferredTool: KagiTool.summarizer,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
icon: Icon(KagiTool.summarizer.icon),
|
// icon: Icon(KagiTool.summarizer.icon),
|
||||||
),
|
// ),
|
||||||
// if (showEarlyAccessFeatures)
|
// if (showEarlyAccessFeatures)
|
||||||
// IconButton(
|
// IconButton(
|
||||||
// color: (activeTool == KagiTool.assistant)
|
// color: (activeTool == KagiTool.assistant)
|
||||||
@@ -199,7 +216,9 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (selectedTabId != null) ReaderButton(),
|
if (selectedTabId != null &&
|
||||||
|
displayedSheet is! ViewTabsSheet)
|
||||||
|
ReaderButton(),
|
||||||
// if (quickAction != null)
|
// if (quickAction != null)
|
||||||
// InkWell(
|
// InkWell(
|
||||||
// onTap: () async {
|
// onTap: () async {
|
||||||
@@ -268,9 +287,7 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ref
|
await context.push(const SearchRoute().location);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.addTab(url: Uri.https('kagi.com'));
|
|
||||||
},
|
},
|
||||||
leadingIcon: const Icon(Icons.add),
|
leadingIcon: const Icon(Icons.add),
|
||||||
child: const Text('Add Tab'),
|
child: const Text('Add Tab'),
|
||||||
@@ -586,14 +603,6 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
leadingIcon: const Icon(Icons.info),
|
leadingIcon: const Icon(Icons.info),
|
||||||
child: const Text('Auth'),
|
child: const Text('Auth'),
|
||||||
),
|
),
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await context
|
|
||||||
.push(WebEngineHardeningRoute().location);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(Icons.info),
|
|
||||||
child: const Text('Pref'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -8,8 +8,14 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
final TabState tab;
|
final TabState tab;
|
||||||
|
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
|
final void Function()? onDoubleTap;
|
||||||
|
|
||||||
const AppBarTitle({required this.tab, this.onTap, super.key});
|
const AppBarTitle({
|
||||||
|
required this.tab,
|
||||||
|
this.onTap,
|
||||||
|
this.onDoubleTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
Icon _securityStatusIcon(BuildContext context) {
|
Icon _securityStatusIcon(BuildContext context) {
|
||||||
if (tab.url.isScheme('http')) {
|
if (tab.url.isScheme('http')) {
|
||||||
@@ -42,6 +48,7 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Skeletonizer(
|
Skeletonizer(
|
||||||
|
|||||||
+39
-27
@@ -5,6 +5,7 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/logger.dart';
|
import 'package:lensai/core/logger.dart';
|
||||||
import 'package:lensai/features/geckoview/domain/providers.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_state.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/providers/web_extensions_state.dart';
|
||||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||||
@@ -49,6 +50,9 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final hasTab =
|
||||||
|
ref.watch(selectedTabProvider.select((value) => value != null));
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
selectedTabStateProvider.select(
|
selectedTabStateProvider.select(
|
||||||
(state) => (tabId: state?.id, isLoading: state?.isLoading),
|
(state) => (tabId: state?.id, isLoading: state?.isLoading),
|
||||||
@@ -69,34 +73,42 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return GeckoView(
|
return Visibility(
|
||||||
preInitializationStep: () async {
|
visible: hasTab,
|
||||||
await ref
|
replacement: SizedBox.expand(
|
||||||
.read(generalSettingsRepositoryProvider.notifier)
|
child: Container(
|
||||||
.fetch()
|
color: Colors.grey[800],
|
||||||
.then((settings) {
|
),
|
||||||
ref
|
),
|
||||||
.read(deleteBrowserDataServiceProvider.notifier)
|
child: GeckoView(
|
||||||
.deleteData(settings.deleteBrowsingDataOnQuit);
|
preInitializationStep: () async {
|
||||||
});
|
await ref
|
||||||
|
.read(generalSettingsRepositoryProvider.notifier)
|
||||||
|
.fetch()
|
||||||
|
.then((settings) {
|
||||||
|
ref
|
||||||
|
.read(deleteBrowserDataServiceProvider.notifier)
|
||||||
|
.deleteData(settings.deleteBrowsingDataOnQuit);
|
||||||
|
});
|
||||||
|
|
||||||
await ref
|
await ref
|
||||||
.read(eventServiceProvider)
|
.read(eventServiceProvider)
|
||||||
.viewReadyStateEvents
|
.viewReadyStateEvents
|
||||||
.firstWhere((state) => state == true)
|
.firstWhere((state) => state == true)
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 3),
|
const Duration(seconds: 3),
|
||||||
onTimeout: () {
|
onTimeout: () {
|
||||||
logger.e(
|
logger.e(
|
||||||
'Browser fragement not reported ready, trying to intitialize anyways',
|
'Browser fragement not reported ready, trying to intitialize anyways',
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
postInitializationStep: () async {
|
postInitializationStep: () async {
|
||||||
await widget.postInitializationStep?.call();
|
await widget.postInitializationStep?.call();
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||||
|
|
||||||
|
class EditUrlDialog extends HookWidget {
|
||||||
|
final Uri initialUrl;
|
||||||
|
|
||||||
|
const EditUrlDialog({required this.initialUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||||
|
final urlController = useTextEditingController(text: initialUrl.toString());
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Edit Address'),
|
||||||
|
content: Form(
|
||||||
|
key: formKey,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: urlController,
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Enter URL',
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a URL';
|
||||||
|
}
|
||||||
|
if (uri_parser.tryParseUrl(value) == null) {
|
||||||
|
return 'Please enter a valid URL';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (formKey.currentState?.validate() ?? false) {
|
||||||
|
Navigator.of(context).pop(Uri.parse(urlController.text));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Edit'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-4
@@ -4,8 +4,10 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
|
import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/providers.dart';
|
import 'package:lensai/core/providers.dart';
|
||||||
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/data/models/drag_data.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/selected_tab.dart';
|
||||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||||
@@ -428,10 +430,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: FloatingActionButton.small(
|
child: FloatingActionButton.small(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ref.read(tabRepositoryProvider.notifier).addTab(
|
await context.push(const SearchRoute().location);
|
||||||
url: Uri.https('setcookie.net'),
|
|
||||||
// contextId: uuid.v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:lensai/features/geckoview/features/search/presentation/widgets/s
|
|||||||
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart';
|
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart';
|
||||||
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/search_suggestions.dart';
|
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/search_suggestions.dart';
|
||||||
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart';
|
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart';
|
||||||
|
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||||
|
|
||||||
class SearchScreen extends HookConsumerWidget {
|
class SearchScreen extends HookConsumerWidget {
|
||||||
final String? initialSearchText;
|
final String? initialSearchText;
|
||||||
@@ -37,13 +38,11 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
ref.listen(
|
ref.listen(
|
||||||
selectedBangDataProvider(),
|
selectedBangDataProvider(),
|
||||||
(previous, next) {
|
(previous, next) {
|
||||||
if (next.hasValue) {
|
if (previous != next) {
|
||||||
if ((previous?.hasValue ?? false) && previous!.value != next.value) {
|
showBangIcon.value = true;
|
||||||
showBangIcon.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedBang.value = next.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectedBang.value = next;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -77,18 +76,31 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
textEditingController: searchTextController,
|
textEditingController: searchTextController,
|
||||||
focusNode: searchFocusNode,
|
focusNode: searchFocusNode,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
onSubmitted: (text) async {
|
onSubmitted: (value) async {
|
||||||
if (Uri.tryParse(text) case final Uri url) {
|
if (value.isNotEmpty) {
|
||||||
await ref
|
var newUrl =
|
||||||
.read(tabRepositoryProvider.notifier)
|
uri_parser.tryParseUrl(value, eagerParsing: true);
|
||||||
.addTab(url: url);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
if (newUrl == null) {
|
||||||
ref
|
final defaultSearchBang = ref
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
.read(selectedBangDataProvider()) ??
|
||||||
.dismiss();
|
await ref.read(defaultSearchBangDataProvider.future);
|
||||||
|
|
||||||
context.pop();
|
newUrl = defaultSearchBang?.getUrl(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newUrl != null) {
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(url: newUrl);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ref
|
||||||
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
|
.dismiss();
|
||||||
|
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
|||||||
class SearchField extends HookConsumerWidget {
|
class SearchField extends HookConsumerWidget {
|
||||||
final TextEditingController textEditingController;
|
final TextEditingController textEditingController;
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
|
final Widget? label;
|
||||||
final bool autofocus;
|
final bool autofocus;
|
||||||
final void Function(String)? onSubmitted;
|
final void Function(String)? onSubmitted;
|
||||||
final bool showSuggestions;
|
final bool showSuggestions;
|
||||||
@@ -25,6 +26,7 @@ class SearchField extends HookConsumerWidget {
|
|||||||
required this.onSubmitted,
|
required this.onSubmitted,
|
||||||
required this.activeBang,
|
required this.activeBang,
|
||||||
required this.showSuggestions,
|
required this.showSuggestions,
|
||||||
|
this.label,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.showBangIcon = true,
|
this.showBangIcon = true,
|
||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
@@ -70,7 +72,7 @@ class SearchField extends HookConsumerWidget {
|
|||||||
child: UrlIcon(activeBang!.getUrl(''), iconSize: 24.0),
|
child: UrlIcon(activeBang!.getUrl(''), iconSize: 24.0),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
label: const Text('Search'),
|
label: label ?? const Text('Search'),
|
||||||
// hintText: 'Ask anything...',
|
// hintText: 'Ask anything...',
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
suffixIcon: hasText
|
suffixIcon: hasText
|
||||||
|
|||||||
+2
-2
@@ -81,8 +81,8 @@ class HistorySuggestions extends HookConsumerWidget {
|
|||||||
subtitle: suggestion.description.mapNotNull(
|
subtitle: suggestion.description.mapNotNull(
|
||||||
(description) => Text(
|
(description) => Text(
|
||||||
description,
|
description,
|
||||||
softWrap: false,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|||||||
+23
-13
@@ -118,20 +118,30 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: MarkdownBody(
|
subtitle: (bodyHasMatch || urlHasMatch)
|
||||||
data: (bodyHasMatch && !urlHasMatch)
|
? MarkdownBody(
|
||||||
? result.content!
|
data: bodyHasMatch
|
||||||
: result.highlightedUrl ?? result.url.toString(),
|
? result.content!
|
||||||
styleSheet: MarkdownStyleSheet(
|
: result.highlightedUrl!,
|
||||||
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
styleSheet: MarkdownStyleSheet(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
a: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurfaceVariant,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
a: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
)
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
: Text(
|
||||||
decoration: TextDecoration.none,
|
result.url.toString(),
|
||||||
),
|
maxLines: 2,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await ref
|
await ref
|
||||||
.read(tabRepositoryProvider.notifier)
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ class QAMemoryChain extends _$QAMemoryChain {
|
|||||||
contextId: contextId,
|
contextId: contextId,
|
||||||
).asRetriever(
|
).asRetriever(
|
||||||
defaultOptions: const VectorStoreRetrieverOptions(
|
defaultOptions: const VectorStoreRetrieverOptions(
|
||||||
searchType: VectorStoreSimilaritySearch(k: 3),
|
searchType: VectorStoreSimilaritySearch(k: 6),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ part of 'qa_memory_chain.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$qAMemoryChainHash() => r'd5efc3fc01a6a88c67164d7eefc06412c9ab7c5c';
|
String _$qAMemoryChainHash() => r'f05ff182416cd8ac8c35f046081652f8964684a8';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ class BangSettingsScreen extends HookConsumerWidget {
|
|||||||
title: 'Kagi Bangs',
|
title: 'Kagi Bangs',
|
||||||
subtitle: 'Automatically syncs every 7 days',
|
subtitle: 'Automatically syncs every 7 days',
|
||||||
),
|
),
|
||||||
|
const BangGroupListTile(
|
||||||
|
group: BangGroup.custom,
|
||||||
|
title: 'Lensai Bangs',
|
||||||
|
subtitle: 'Automatically syncs every 7 days',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
final Color? suggestionHighlightColor;
|
final Color? suggestionHighlightColor;
|
||||||
final bool? enableIMEPersonalizedLearning;
|
final bool? enableIMEPersonalizedLearning;
|
||||||
final TapRegionCallback? onTapOutside;
|
final TapRegionCallback? onTapOutside;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
const AutoSuggestTextField({
|
const AutoSuggestTextField({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -53,6 +54,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
this.suggestionHighlightColor,
|
this.suggestionHighlightColor,
|
||||||
this.enableIMEPersonalizedLearning = true,
|
this.enableIMEPersonalizedLearning = true,
|
||||||
this.onTapOutside,
|
this.onTapOutside,
|
||||||
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool _suggestionHasMatch() =>
|
bool _suggestionHasMatch() =>
|
||||||
@@ -69,8 +71,12 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
if (suggestion != null)
|
if (suggestion != null)
|
||||||
AbsorbPointer(
|
AbsorbPointer(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
minLines: minLines,
|
||||||
|
maxLines: maxLines,
|
||||||
|
maxLength: maxLength,
|
||||||
decoration: baseDecoration.copyWith(
|
decoration: baseDecoration.copyWith(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
label: HookBuilder(
|
label: HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final text = useListenableSelector(
|
final text = useListenableSelector(
|
||||||
@@ -107,6 +113,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return RichText(
|
return RichText(
|
||||||
|
maxLines: maxLines,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: text,
|
text: text,
|
||||||
style: (style ?? Theme.of(context).textTheme.bodyLarge)
|
style: (style ?? Theme.of(context).textTheme.bodyLarge)
|
||||||
@@ -168,6 +175,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
cursorColor: cursorColor,
|
cursorColor: cursorColor,
|
||||||
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning ?? true,
|
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning ?? true,
|
||||||
onTapOutside: onTapOutside,
|
onTapOutside: onTapOutside,
|
||||||
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
|
final _domainRegex = RegExp(r'\.[a-zA-Z]{2,}$');
|
||||||
|
const _supportedSchemes = {'https', 'http', 'ftp', 'file', 'about'};
|
||||||
|
|
||||||
Uri? tryParseUrl(String? input, {bool eagerParsing = false}) {
|
Uri? tryParseUrl(String? input, {bool eagerParsing = false}) {
|
||||||
if (input != null) {
|
if (input != null) {
|
||||||
var uri = Uri.tryParse(input);
|
var uri = Uri.tryParse(input);
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
if (uri.authority.isEmpty && eagerParsing) {
|
if (uri.authority.isEmpty && eagerParsing) {
|
||||||
//When there is no scheme, there will be no authority/host and all becomes a path
|
if (uri.pathSegments.isNotEmpty) {
|
||||||
//so we make sure there are at least 2 segments where the first one looks like a domain
|
if (_domainRegex.hasMatch(uri.pathSegments.first)) {
|
||||||
if (uri.pathSegments.length > 1 &&
|
uri = Uri.tryParse('https://$input');
|
||||||
RegExp(r'.[a-z]{2,}$').hasMatch(uri.pathSegments.first)) {
|
}
|
||||||
uri = Uri.tryParse('https://$input');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri != null &&
|
if (uri != null) {
|
||||||
uri.authority.isNotEmpty &&
|
if (uri.isScheme('about')) {
|
||||||
(uri.isScheme('http') || uri.isScheme('https') || !uri.hasScheme)) {
|
return uri;
|
||||||
return uri;
|
}
|
||||||
|
|
||||||
|
if (uri.authority.isNotEmpty) {
|
||||||
|
if (_supportedSchemes.contains(uri.scheme) || !uri.hasScheme) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user