pretty good
This commit is contained in:
@@ -97,7 +97,7 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat.Light"
|
||||
android:theme="@style/AddonsActivityTheme"
|
||||
android:name="eu.lensai.flutter_mozilla_components.addons.AddonsActivity"
|
||||
android:label="@string/mozac_feature_addons_addons"
|
||||
android:exported="false"
|
||||
|
||||
@@ -25,4 +25,8 @@
|
||||
<item name="android:background">?android:attr/colorBackground</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="AddonsActivityTheme" parent="Theme.AppCompat.Light">
|
||||
<item name="mozac_primary_text_color">@color/photonDarkGrey90</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -14,7 +14,7 @@ import 'package:lensai/features/geckoview/features/browser/presentation/screens/
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_edit.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_list.dart';
|
||||
import 'package:lensai/features/search/presentation/screens/search.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/presentation/screens/search.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/bang_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/general_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
||||
|
||||
@@ -274,6 +274,18 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<BrowserIcon> getUrlIcon(Uri url) async {
|
||||
final cachedIcon = await getCachedIcon(url);
|
||||
|
||||
if (cachedIcon != null) {
|
||||
return cachedIcon;
|
||||
}
|
||||
|
||||
return fetchPageInfo(url).then(
|
||||
(result) => result.flatMap((pageInfo) => pageInfo.favicon!).value,
|
||||
);
|
||||
}
|
||||
|
||||
// Future<Uri?> tryUpgradeToHttps(Uri httpUri) async {
|
||||
// if (httpUri.isScheme('https')) {
|
||||
// return httpUri;
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'generic_website.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$genericWebsiteServiceHash() =>
|
||||
r'27430ba17a04b0fa3cca20abf00f9d1c15fa843a';
|
||||
r'4c8c1c9ad430ab01f637f02ec97ee4ce5202975e';
|
||||
|
||||
/// See also [GenericWebsiteService].
|
||||
@ProviderFor(GenericWebsiteService)
|
||||
|
||||
@@ -57,17 +57,6 @@ Stream<List<SearchHistoryEntry>> searchHistory(Ref ref) {
|
||||
return repository.watchSearchHistory(limit: 3); //TODO: make count dynamic
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Future<BangData> bangDataEnsureIcon(
|
||||
Ref ref,
|
||||
BangData bang,
|
||||
) {
|
||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||
return repository.ensureIconAvailable(bang).then(
|
||||
(value) => value.value,
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<DateTime?> lastSyncOfGroup(
|
||||
Ref ref,
|
||||
|
||||
@@ -387,138 +387,6 @@ final searchHistoryProvider =
|
||||
// ignore: unused_element
|
||||
typedef SearchHistoryRef
|
||||
= AutoDisposeStreamProviderRef<List<SearchHistoryEntry>>;
|
||||
String _$bangDataEnsureIconHash() =>
|
||||
r'423b8e92fd89ec328c2fcc8f9e55365c30633fd2';
|
||||
|
||||
/// See also [bangDataEnsureIcon].
|
||||
@ProviderFor(bangDataEnsureIcon)
|
||||
const bangDataEnsureIconProvider = BangDataEnsureIconFamily();
|
||||
|
||||
/// See also [bangDataEnsureIcon].
|
||||
class BangDataEnsureIconFamily extends Family<AsyncValue<BangData>> {
|
||||
/// See also [bangDataEnsureIcon].
|
||||
const BangDataEnsureIconFamily();
|
||||
|
||||
/// See also [bangDataEnsureIcon].
|
||||
BangDataEnsureIconProvider call(
|
||||
BangData bang,
|
||||
) {
|
||||
return BangDataEnsureIconProvider(
|
||||
bang,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
BangDataEnsureIconProvider getProviderOverride(
|
||||
covariant BangDataEnsureIconProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.bang,
|
||||
);
|
||||
}
|
||||
|
||||
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'bangDataEnsureIconProvider';
|
||||
}
|
||||
|
||||
/// See also [bangDataEnsureIcon].
|
||||
class BangDataEnsureIconProvider extends AutoDisposeFutureProvider<BangData> {
|
||||
/// See also [bangDataEnsureIcon].
|
||||
BangDataEnsureIconProvider(
|
||||
BangData bang,
|
||||
) : this._internal(
|
||||
(ref) => bangDataEnsureIcon(
|
||||
ref as BangDataEnsureIconRef,
|
||||
bang,
|
||||
),
|
||||
from: bangDataEnsureIconProvider,
|
||||
name: r'bangDataEnsureIconProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$bangDataEnsureIconHash,
|
||||
dependencies: BangDataEnsureIconFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
BangDataEnsureIconFamily._allTransitiveDependencies,
|
||||
bang: bang,
|
||||
);
|
||||
|
||||
BangDataEnsureIconProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.bang,
|
||||
}) : super.internal();
|
||||
|
||||
final BangData bang;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<BangData> Function(BangDataEnsureIconRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: BangDataEnsureIconProvider._internal(
|
||||
(ref) => create(ref as BangDataEnsureIconRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
bang: bang,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<BangData> createElement() {
|
||||
return _BangDataEnsureIconProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is BangDataEnsureIconProvider && other.bang == bang;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, bang.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin BangDataEnsureIconRef on AutoDisposeFutureProviderRef<BangData> {
|
||||
/// The parameter `bang` of this provider.
|
||||
BangData get bang;
|
||||
}
|
||||
|
||||
class _BangDataEnsureIconProviderElement
|
||||
extends AutoDisposeFutureProviderElement<BangData>
|
||||
with BangDataEnsureIconRef {
|
||||
_BangDataEnsureIconProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
BangData get bang => (origin as BangDataEnsureIconProvider).bang;
|
||||
}
|
||||
|
||||
String _$lastSyncOfGroupHash() => r'23d07f3132ba9bb35a31f74e3a69698d31d4c569';
|
||||
|
||||
/// See also [lastSyncOfGroup].
|
||||
|
||||
@@ -111,26 +111,6 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
.removeSearchEntry(searchQuery);
|
||||
}
|
||||
|
||||
Future<Result<BangData>> ensureIconAvailable(BangData bang) async {
|
||||
if (bang.icon != null) {
|
||||
return Result.success(bang);
|
||||
}
|
||||
|
||||
final url = bang.getUrl('');
|
||||
|
||||
final websiteProvider = ref.read(genericWebsiteServiceProvider.notifier);
|
||||
final cachedIcon = await websiteProvider.getCachedIcon(url);
|
||||
|
||||
if (cachedIcon != null) {
|
||||
return Result.success(bang.copyWith.icon(cachedIcon));
|
||||
}
|
||||
|
||||
return websiteProvider.fetchPageInfo(url).then(
|
||||
(result) => result
|
||||
.flatMap((pageInfo) => bang.copyWith.icon(pageInfo.favicon)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> resetFrequencies() {
|
||||
return ref.read(bangDatabaseProvider).bangFrequency.deleteAll();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'data.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangDataRepositoryHash() =>
|
||||
r'2c009519cf02df4d3cbb6768dbe55e9eb696e126';
|
||||
r'de89d7089ce6a92015bee233f08d920981518793';
|
||||
|
||||
/// See also [BangDataRepository].
|
||||
@ProviderFor(BangDataRepository)
|
||||
|
||||
@@ -13,12 +13,13 @@ class BangSearch extends _$BangSearch {
|
||||
|
||||
Future<void> search(String input) async {
|
||||
if (input.isNotEmpty) {
|
||||
await ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.queryBangs(input)
|
||||
.get()
|
||||
.then(_streamController.add);
|
||||
await ref.read(bangDatabaseProvider).bangDao.queryBangs(input).get().then(
|
||||
(value) {
|
||||
if (!_streamController.isClosed) {
|
||||
_streamController.add(value);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'search.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangSearchHash() => r'632ddd09a1198394884cb5c27a2bde7850c0515b';
|
||||
String _$bangSearchHash() => r'abf9ff5badf2ecfbcbde148fd2c849beac83bec4';
|
||||
|
||||
/// See also [BangSearch].
|
||||
@ProviderFor(BangSearch)
|
||||
|
||||
@@ -3,7 +3,7 @@ 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/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
|
||||
class BangDetails extends HookConsumerWidget {
|
||||
@@ -38,7 +38,7 @@ class BangDetails extends HookConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
BangIcon(bangData),
|
||||
UrlIcon(bangData.getUrl(''), iconSize: 34.0),
|
||||
const SizedBox(width: 12.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class BangIcon extends HookConsumerWidget {
|
||||
final double iconSize;
|
||||
final BangData bangData;
|
||||
|
||||
const BangIcon(this.bangData, {this.iconSize = 34.0, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bangAsync = ref.watch(bangDataEnsureIconProvider(bangData));
|
||||
final bang = bangAsync.valueOrNull ?? bangData;
|
||||
|
||||
return Skeletonizer(
|
||||
enabled: bangAsync.isLoading,
|
||||
child: SizedBox.square(
|
||||
dimension: iconSize,
|
||||
child: (bang.icon != null)
|
||||
? RawImage(
|
||||
image: bang.icon?.image.value,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fit: BoxFit.fill,
|
||||
)
|
||||
: Icon(
|
||||
MdiIcons.web,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/search/domain/providers/search_suggestions.dart';
|
||||
import 'package:lensai/features/search/presentation/widgets/search_field.dart';
|
||||
import 'package:lensai/features/search/presentation/widgets/search_suggestion_list.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/providers/search_suggestions.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_field.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/search_suggestions.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
|
||||
class SiteSearch extends HookConsumerWidget {
|
||||
@@ -70,7 +70,7 @@ class SiteSearch extends HookConsumerWidget {
|
||||
width: double.maxFinite,
|
||||
child: SelectableChips(
|
||||
itemId: (bang) => bang.trigger,
|
||||
itemAvatar: (bang) => BangIcon(bang, iconSize: 20),
|
||||
itemAvatar: (bang) => UrlIcon(bang.getUrl(''), iconSize: 20),
|
||||
itemLabel: (bang) => Text(bang.websiteName),
|
||||
availableItems: availableBangs,
|
||||
selectedItem: selectedBang,
|
||||
@@ -98,9 +98,10 @@ class SiteSearch extends HookConsumerWidget {
|
||||
SearchField(
|
||||
textEditingController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
onFieldSubmitted: (_) async {
|
||||
onSubmitted: (_) async {
|
||||
await submitSearch(searchTextController.text);
|
||||
},
|
||||
showSuggestions: false,
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 150),
|
||||
@@ -111,10 +112,12 @@ class SiteSearch extends HookConsumerWidget {
|
||||
shrinkWrap: true,
|
||||
controller: controller,
|
||||
slivers: [
|
||||
SearchSuggestionList(
|
||||
SearchTermSuggestions(
|
||||
searchTextController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
submitSearch: submitSearch,
|
||||
showHistory: false,
|
||||
showChips: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -74,7 +74,13 @@ class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository {
|
||||
ellipsis: ellipsis,
|
||||
)
|
||||
.get()
|
||||
.then(_streamController.add);
|
||||
.then(
|
||||
(value) {
|
||||
if (!_streamController.isClosed) {
|
||||
_streamController.add(value);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'search.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatArchiveSearchRepositoryHash() =>
|
||||
r'6762958d43d2efc95f5d9848bfcae41436564f2b';
|
||||
r'407023f1ee8683f21d4263dccc9eab1d7a40278e';
|
||||
|
||||
/// See also [ChatArchiveSearchRepository].
|
||||
@ProviderFor(ChatArchiveSearchRepository)
|
||||
|
||||
@@ -29,7 +29,9 @@ class BottomSheetExtend extends _$BottomSheetExtend {
|
||||
late StreamController<double> _extentStreamController;
|
||||
|
||||
void add(double extent) {
|
||||
_extentStreamController.add(extent);
|
||||
if (!_extentStreamController.isClosed) {
|
||||
_extentStreamController.add(extent);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -23,7 +23,7 @@ final bottomSheetControllerProvider =
|
||||
);
|
||||
|
||||
typedef _$BottomSheetController = AutoDisposeNotifier<Sheet?>;
|
||||
String _$bottomSheetExtendHash() => r'81ae254499d1d7f57309274b0f18747b94d6dda3';
|
||||
String _$bottomSheetExtendHash() => r'7e11b9047c15bdeb4dfcb488a8573daadb64e25c';
|
||||
|
||||
/// See also [BottomSheetExtend].
|
||||
@ProviderFor(BottomSheetExtend)
|
||||
|
||||
@@ -107,6 +107,17 @@ GeckoTabContentService tabContentService(Ref ref) {
|
||||
return service;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
GeckoSuggestionsService engineSuggestionsService(Ref ref) {
|
||||
final service = GeckoSuggestionsService.setUp();
|
||||
|
||||
ref.onDispose(() {
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class EngineReadyState extends _$EngineReadyState {
|
||||
@override
|
||||
|
||||
@@ -74,6 +74,25 @@ final tabContentServiceProvider = Provider<GeckoTabContentService>.internal(
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef TabContentServiceRef = ProviderRef<GeckoTabContentService>;
|
||||
String _$engineSuggestionsServiceHash() =>
|
||||
r'f7414b335564578b2c7f6a86baf8bf13f2d5ba2d';
|
||||
|
||||
/// See also [engineSuggestionsService].
|
||||
@ProviderFor(engineSuggestionsService)
|
||||
final engineSuggestionsServiceProvider =
|
||||
Provider<GeckoSuggestionsService>.internal(
|
||||
engineSuggestionsService,
|
||||
name: r'engineSuggestionsServiceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$engineSuggestionsServiceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef EngineSuggestionsServiceRef = ProviderRef<GeckoSuggestionsService>;
|
||||
String _$selectedTabSessionNotifierHash() =>
|
||||
r'aef19991d5b05a2bb28e8a6ccb57dc75c2f3148f';
|
||||
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/data/models/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/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';
|
||||
@@ -87,27 +92,48 @@ class ShowFindInPage extends _$ShowFindInPage {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
List<String> availableTabIds(
|
||||
EquatableCollection<List<String>> availableTabIds(
|
||||
Ref ref,
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
final containerTabs = ref.watch(
|
||||
containerTabIdsProvider(containerId).select((value) => value.valueOrNull),
|
||||
containerTabIdsProvider(containerFilter)
|
||||
.select((value) => value.valueOrNull),
|
||||
);
|
||||
final tabStates = ref.watch(tabListProvider);
|
||||
final tabList = ref.watch(tabListProvider);
|
||||
|
||||
return containerTabs?.where((tabId) => tabStates.contains(tabId)).toList() ??
|
||||
[];
|
||||
return EquatableCollection(
|
||||
containerTabs?.where((tabId) => tabList.contains(tabId)).toList() ?? [],
|
||||
immutable: true,
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
List<String> seamlessFilteredTabs(
|
||||
EquatableCollection<Map<String, TabState>> availableTabStates(
|
||||
Ref ref,
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
final availableTabs = ref.watch(availableTabIdsProvider(containerFilter));
|
||||
final tabStates = ref.watch(tabStatesProvider);
|
||||
|
||||
return EquatableCollection(
|
||||
{
|
||||
for (final tabId in availableTabs.collection)
|
||||
if (tabStates.containsKey(tabId)) tabId: tabStates[tabId]!,
|
||||
},
|
||||
immutable: true,
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableCollection<List<String>> seamlessFilteredTabIds(
|
||||
Ref ref,
|
||||
TabSearchPartition searchPartition,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
final tabSearchResults = ref
|
||||
.watch(
|
||||
tabSearchRepositoryProvider.select(
|
||||
tabSearchRepositoryProvider(searchPartition).select(
|
||||
(value) => EquatableCollection(
|
||||
value.valueOrNull?.map((tab) => tab.id).toList(),
|
||||
immutable: true,
|
||||
@@ -116,17 +142,74 @@ List<String> seamlessFilteredTabs(
|
||||
)
|
||||
.collection;
|
||||
|
||||
final availableTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(containerId).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
final availableTabs = ref.watch(availableTabIdsProvider(containerFilter));
|
||||
|
||||
if (tabSearchResults == null) {
|
||||
return availableTabs;
|
||||
}
|
||||
|
||||
return tabSearchResults.where((tab) => availableTabs.contains(tab)).toList();
|
||||
return EquatableCollection(
|
||||
tabSearchResults
|
||||
.where((tab) => availableTabs.collection.contains(tab))
|
||||
.toList(),
|
||||
immutable: true,
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableCollection<List<TabPreview>> seamlessFilteredTabPreviews(
|
||||
Ref ref,
|
||||
TabSearchPartition searchPartition,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
final tabSearchResults = ref
|
||||
.watch(
|
||||
tabSearchRepositoryProvider(searchPartition).select(
|
||||
(value) => EquatableCollection(
|
||||
value.valueOrNull,
|
||||
immutable: true,
|
||||
),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
final availableTabStates =
|
||||
ref.watch(availableTabStatesProvider(containerFilter));
|
||||
|
||||
if (tabSearchResults == null) {
|
||||
return EquatableCollection(
|
||||
availableTabStates.collection.values
|
||||
.map(
|
||||
(state) => TabPreview(
|
||||
id: state.id,
|
||||
title: state.title,
|
||||
icon: state.icon,
|
||||
url: state.url,
|
||||
highlightedUrl: null,
|
||||
content: null,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
immutable: true,
|
||||
);
|
||||
}
|
||||
|
||||
return EquatableCollection(
|
||||
tabSearchResults
|
||||
.where((tab) => availableTabStates.collection.containsKey(tab.id))
|
||||
.map((tab) {
|
||||
return TabPreview(
|
||||
id: tab.id,
|
||||
title: tab.title ?? availableTabStates.collection[tab.id]!.title,
|
||||
icon: null,
|
||||
url: tab.cleanUrl.mapNotNull(Uri.tryParse) ??
|
||||
availableTabStates.collection[tab.id]!.url,
|
||||
highlightedUrl: tab.url,
|
||||
content: tab.extractedContent ?? tab.fullContent,
|
||||
);
|
||||
})
|
||||
.whereType<TabPreview>()
|
||||
.toList(),
|
||||
immutable: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,23 +158,23 @@ class _SelectedBangDataProviderElement
|
||||
String? get domain => (origin as SelectedBangDataProvider).domain;
|
||||
}
|
||||
|
||||
String _$availableTabIdsHash() => r'646d42c747d815496849c7342903caf48258a8ab';
|
||||
String _$availableTabIdsHash() => r'd8b55b012b38111c8d5e069987a55645d1a71b89';
|
||||
|
||||
/// See also [availableTabIds].
|
||||
@ProviderFor(availableTabIds)
|
||||
const availableTabIdsProvider = AvailableTabIdsFamily();
|
||||
|
||||
/// See also [availableTabIds].
|
||||
class AvailableTabIdsFamily extends Family<List<String>> {
|
||||
class AvailableTabIdsFamily extends Family<EquatableCollection<List<String>>> {
|
||||
/// See also [availableTabIds].
|
||||
const AvailableTabIdsFamily();
|
||||
|
||||
/// See also [availableTabIds].
|
||||
AvailableTabIdsProvider call(
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
return AvailableTabIdsProvider(
|
||||
containerId,
|
||||
containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ class AvailableTabIdsFamily extends Family<List<String>> {
|
||||
covariant AvailableTabIdsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.containerId,
|
||||
provider.containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,14 +203,15 @@ class AvailableTabIdsFamily extends Family<List<String>> {
|
||||
}
|
||||
|
||||
/// See also [availableTabIds].
|
||||
class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
|
||||
class AvailableTabIdsProvider
|
||||
extends AutoDisposeProvider<EquatableCollection<List<String>>> {
|
||||
/// See also [availableTabIds].
|
||||
AvailableTabIdsProvider(
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) : this._internal(
|
||||
(ref) => availableTabIds(
|
||||
ref as AvailableTabIdsRef,
|
||||
containerId,
|
||||
containerFilter,
|
||||
),
|
||||
from: availableTabIdsProvider,
|
||||
name: r'availableTabIdsProvider',
|
||||
@@ -221,7 +222,7 @@ class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
|
||||
dependencies: AvailableTabIdsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AvailableTabIdsFamily._allTransitiveDependencies,
|
||||
containerId: containerId,
|
||||
containerFilter: containerFilter,
|
||||
);
|
||||
|
||||
AvailableTabIdsProvider._internal(
|
||||
@@ -231,14 +232,15 @@ class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.containerId,
|
||||
required this.containerFilter,
|
||||
}) : super.internal();
|
||||
|
||||
final String? containerId;
|
||||
final ContainerFilter containerFilter;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
List<String> Function(AvailableTabIdsRef provider) create,
|
||||
EquatableCollection<List<String>> Function(AvailableTabIdsRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
@@ -249,25 +251,27 @@ class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
containerId: containerId,
|
||||
containerFilter: containerFilter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<List<String>> createElement() {
|
||||
AutoDisposeProviderElement<EquatableCollection<List<String>>>
|
||||
createElement() {
|
||||
return _AvailableTabIdsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AvailableTabIdsProvider && other.containerId == containerId;
|
||||
return other is AvailableTabIdsProvider &&
|
||||
other.containerFilter == containerFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -275,46 +279,50 @@ class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AvailableTabIdsRef on AutoDisposeProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
mixin AvailableTabIdsRef
|
||||
on AutoDisposeProviderRef<EquatableCollection<List<String>>> {
|
||||
/// The parameter `containerFilter` of this provider.
|
||||
ContainerFilter get containerFilter;
|
||||
}
|
||||
|
||||
class _AvailableTabIdsProviderElement
|
||||
extends AutoDisposeProviderElement<List<String>> with AvailableTabIdsRef {
|
||||
extends AutoDisposeProviderElement<EquatableCollection<List<String>>>
|
||||
with AvailableTabIdsRef {
|
||||
_AvailableTabIdsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get containerId => (origin as AvailableTabIdsProvider).containerId;
|
||||
ContainerFilter get containerFilter =>
|
||||
(origin as AvailableTabIdsProvider).containerFilter;
|
||||
}
|
||||
|
||||
String _$seamlessFilteredTabsHash() =>
|
||||
r'1abfb0e9ae834cdde5ab64b6174f24f45f36a272';
|
||||
String _$availableTabStatesHash() =>
|
||||
r'ca42dc8cb88070fb58ab085ab6c9bcbf31ef4f0b';
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
@ProviderFor(seamlessFilteredTabs)
|
||||
const seamlessFilteredTabsProvider = SeamlessFilteredTabsFamily();
|
||||
/// See also [availableTabStates].
|
||||
@ProviderFor(availableTabStates)
|
||||
const availableTabStatesProvider = AvailableTabStatesFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
class SeamlessFilteredTabsFamily extends Family<List<String>> {
|
||||
/// See also [seamlessFilteredTabs].
|
||||
const SeamlessFilteredTabsFamily();
|
||||
/// See also [availableTabStates].
|
||||
class AvailableTabStatesFamily
|
||||
extends Family<EquatableCollection<Map<String, TabState>>> {
|
||||
/// See also [availableTabStates].
|
||||
const AvailableTabStatesFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
SeamlessFilteredTabsProvider call(
|
||||
String? containerId,
|
||||
/// See also [availableTabStates].
|
||||
AvailableTabStatesProvider call(
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
return SeamlessFilteredTabsProvider(
|
||||
containerId,
|
||||
return AvailableTabStatesProvider(
|
||||
containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SeamlessFilteredTabsProvider getProviderOverride(
|
||||
covariant SeamlessFilteredTabsProvider provider,
|
||||
AvailableTabStatesProvider getProviderOverride(
|
||||
covariant AvailableTabStatesProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.containerId,
|
||||
provider.containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -330,76 +338,80 @@ class SeamlessFilteredTabsFamily extends Family<List<String>> {
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'seamlessFilteredTabsProvider';
|
||||
String? get name => r'availableTabStatesProvider';
|
||||
}
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
class SeamlessFilteredTabsProvider extends AutoDisposeProvider<List<String>> {
|
||||
/// See also [seamlessFilteredTabs].
|
||||
SeamlessFilteredTabsProvider(
|
||||
String? containerId,
|
||||
/// See also [availableTabStates].
|
||||
class AvailableTabStatesProvider
|
||||
extends AutoDisposeProvider<EquatableCollection<Map<String, TabState>>> {
|
||||
/// See also [availableTabStates].
|
||||
AvailableTabStatesProvider(
|
||||
ContainerFilter containerFilter,
|
||||
) : this._internal(
|
||||
(ref) => seamlessFilteredTabs(
|
||||
ref as SeamlessFilteredTabsRef,
|
||||
containerId,
|
||||
(ref) => availableTabStates(
|
||||
ref as AvailableTabStatesRef,
|
||||
containerFilter,
|
||||
),
|
||||
from: seamlessFilteredTabsProvider,
|
||||
name: r'seamlessFilteredTabsProvider',
|
||||
from: availableTabStatesProvider,
|
||||
name: r'availableTabStatesProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$seamlessFilteredTabsHash,
|
||||
dependencies: SeamlessFilteredTabsFamily._dependencies,
|
||||
: _$availableTabStatesHash,
|
||||
dependencies: AvailableTabStatesFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SeamlessFilteredTabsFamily._allTransitiveDependencies,
|
||||
containerId: containerId,
|
||||
AvailableTabStatesFamily._allTransitiveDependencies,
|
||||
containerFilter: containerFilter,
|
||||
);
|
||||
|
||||
SeamlessFilteredTabsProvider._internal(
|
||||
AvailableTabStatesProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.containerId,
|
||||
required this.containerFilter,
|
||||
}) : super.internal();
|
||||
|
||||
final String? containerId;
|
||||
final ContainerFilter containerFilter;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
List<String> Function(SeamlessFilteredTabsRef provider) create,
|
||||
EquatableCollection<Map<String, TabState>> Function(
|
||||
AvailableTabStatesRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SeamlessFilteredTabsProvider._internal(
|
||||
(ref) => create(ref as SeamlessFilteredTabsRef),
|
||||
override: AvailableTabStatesProvider._internal(
|
||||
(ref) => create(ref as AvailableTabStatesRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
containerId: containerId,
|
||||
containerFilter: containerFilter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<List<String>> createElement() {
|
||||
return _SeamlessFilteredTabsProviderElement(this);
|
||||
AutoDisposeProviderElement<EquatableCollection<Map<String, TabState>>>
|
||||
createElement() {
|
||||
return _AvailableTabStatesProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SeamlessFilteredTabsProvider &&
|
||||
other.containerId == containerId;
|
||||
return other is AvailableTabStatesProvider &&
|
||||
other.containerFilter == containerFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -407,19 +419,333 @@ class SeamlessFilteredTabsProvider extends AutoDisposeProvider<List<String>> {
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SeamlessFilteredTabsRef on AutoDisposeProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
mixin AvailableTabStatesRef
|
||||
on AutoDisposeProviderRef<EquatableCollection<Map<String, TabState>>> {
|
||||
/// The parameter `containerFilter` of this provider.
|
||||
ContainerFilter get containerFilter;
|
||||
}
|
||||
|
||||
class _SeamlessFilteredTabsProviderElement
|
||||
extends AutoDisposeProviderElement<List<String>>
|
||||
with SeamlessFilteredTabsRef {
|
||||
_SeamlessFilteredTabsProviderElement(super.provider);
|
||||
class _AvailableTabStatesProviderElement extends AutoDisposeProviderElement<
|
||||
EquatableCollection<Map<String, TabState>>> with AvailableTabStatesRef {
|
||||
_AvailableTabStatesProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get containerId =>
|
||||
(origin as SeamlessFilteredTabsProvider).containerId;
|
||||
ContainerFilter get containerFilter =>
|
||||
(origin as AvailableTabStatesProvider).containerFilter;
|
||||
}
|
||||
|
||||
String _$seamlessFilteredTabIdsHash() =>
|
||||
r'2e29ca53c35be89972d3f9aea24198fe1feb9e05';
|
||||
|
||||
/// See also [seamlessFilteredTabIds].
|
||||
@ProviderFor(seamlessFilteredTabIds)
|
||||
const seamlessFilteredTabIdsProvider = SeamlessFilteredTabIdsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabIds].
|
||||
class SeamlessFilteredTabIdsFamily
|
||||
extends Family<EquatableCollection<List<String>>> {
|
||||
/// See also [seamlessFilteredTabIds].
|
||||
const SeamlessFilteredTabIdsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabIds].
|
||||
SeamlessFilteredTabIdsProvider call(
|
||||
TabSearchPartition searchPartition,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
return SeamlessFilteredTabIdsProvider(
|
||||
searchPartition,
|
||||
containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SeamlessFilteredTabIdsProvider getProviderOverride(
|
||||
covariant SeamlessFilteredTabIdsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.searchPartition,
|
||||
provider.containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
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'seamlessFilteredTabIdsProvider';
|
||||
}
|
||||
|
||||
/// See also [seamlessFilteredTabIds].
|
||||
class SeamlessFilteredTabIdsProvider
|
||||
extends AutoDisposeProvider<EquatableCollection<List<String>>> {
|
||||
/// See also [seamlessFilteredTabIds].
|
||||
SeamlessFilteredTabIdsProvider(
|
||||
TabSearchPartition searchPartition,
|
||||
ContainerFilter containerFilter,
|
||||
) : this._internal(
|
||||
(ref) => seamlessFilteredTabIds(
|
||||
ref as SeamlessFilteredTabIdsRef,
|
||||
searchPartition,
|
||||
containerFilter,
|
||||
),
|
||||
from: seamlessFilteredTabIdsProvider,
|
||||
name: r'seamlessFilteredTabIdsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$seamlessFilteredTabIdsHash,
|
||||
dependencies: SeamlessFilteredTabIdsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SeamlessFilteredTabIdsFamily._allTransitiveDependencies,
|
||||
searchPartition: searchPartition,
|
||||
containerFilter: containerFilter,
|
||||
);
|
||||
|
||||
SeamlessFilteredTabIdsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.searchPartition,
|
||||
required this.containerFilter,
|
||||
}) : super.internal();
|
||||
|
||||
final TabSearchPartition searchPartition;
|
||||
final ContainerFilter containerFilter;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
EquatableCollection<List<String>> Function(
|
||||
SeamlessFilteredTabIdsRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SeamlessFilteredTabIdsProvider._internal(
|
||||
(ref) => create(ref as SeamlessFilteredTabIdsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
searchPartition: searchPartition,
|
||||
containerFilter: containerFilter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<EquatableCollection<List<String>>>
|
||||
createElement() {
|
||||
return _SeamlessFilteredTabIdsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SeamlessFilteredTabIdsProvider &&
|
||||
other.searchPartition == searchPartition &&
|
||||
other.containerFilter == containerFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, searchPartition.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SeamlessFilteredTabIdsRef
|
||||
on AutoDisposeProviderRef<EquatableCollection<List<String>>> {
|
||||
/// The parameter `searchPartition` of this provider.
|
||||
TabSearchPartition get searchPartition;
|
||||
|
||||
/// The parameter `containerFilter` of this provider.
|
||||
ContainerFilter get containerFilter;
|
||||
}
|
||||
|
||||
class _SeamlessFilteredTabIdsProviderElement
|
||||
extends AutoDisposeProviderElement<EquatableCollection<List<String>>>
|
||||
with SeamlessFilteredTabIdsRef {
|
||||
_SeamlessFilteredTabIdsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
TabSearchPartition get searchPartition =>
|
||||
(origin as SeamlessFilteredTabIdsProvider).searchPartition;
|
||||
@override
|
||||
ContainerFilter get containerFilter =>
|
||||
(origin as SeamlessFilteredTabIdsProvider).containerFilter;
|
||||
}
|
||||
|
||||
String _$seamlessFilteredTabPreviewsHash() =>
|
||||
r'7a70eb2f3f59de5188d94a73790e39c339a6293b';
|
||||
|
||||
/// See also [seamlessFilteredTabPreviews].
|
||||
@ProviderFor(seamlessFilteredTabPreviews)
|
||||
const seamlessFilteredTabPreviewsProvider = SeamlessFilteredTabPreviewsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabPreviews].
|
||||
class SeamlessFilteredTabPreviewsFamily
|
||||
extends Family<EquatableCollection<List<TabPreview>>> {
|
||||
/// See also [seamlessFilteredTabPreviews].
|
||||
const SeamlessFilteredTabPreviewsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabPreviews].
|
||||
SeamlessFilteredTabPreviewsProvider call(
|
||||
TabSearchPartition searchPartition,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
return SeamlessFilteredTabPreviewsProvider(
|
||||
searchPartition,
|
||||
containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SeamlessFilteredTabPreviewsProvider getProviderOverride(
|
||||
covariant SeamlessFilteredTabPreviewsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.searchPartition,
|
||||
provider.containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
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'seamlessFilteredTabPreviewsProvider';
|
||||
}
|
||||
|
||||
/// See also [seamlessFilteredTabPreviews].
|
||||
class SeamlessFilteredTabPreviewsProvider
|
||||
extends AutoDisposeProvider<EquatableCollection<List<TabPreview>>> {
|
||||
/// See also [seamlessFilteredTabPreviews].
|
||||
SeamlessFilteredTabPreviewsProvider(
|
||||
TabSearchPartition searchPartition,
|
||||
ContainerFilter containerFilter,
|
||||
) : this._internal(
|
||||
(ref) => seamlessFilteredTabPreviews(
|
||||
ref as SeamlessFilteredTabPreviewsRef,
|
||||
searchPartition,
|
||||
containerFilter,
|
||||
),
|
||||
from: seamlessFilteredTabPreviewsProvider,
|
||||
name: r'seamlessFilteredTabPreviewsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$seamlessFilteredTabPreviewsHash,
|
||||
dependencies: SeamlessFilteredTabPreviewsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SeamlessFilteredTabPreviewsFamily._allTransitiveDependencies,
|
||||
searchPartition: searchPartition,
|
||||
containerFilter: containerFilter,
|
||||
);
|
||||
|
||||
SeamlessFilteredTabPreviewsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.searchPartition,
|
||||
required this.containerFilter,
|
||||
}) : super.internal();
|
||||
|
||||
final TabSearchPartition searchPartition;
|
||||
final ContainerFilter containerFilter;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
EquatableCollection<List<TabPreview>> Function(
|
||||
SeamlessFilteredTabPreviewsRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SeamlessFilteredTabPreviewsProvider._internal(
|
||||
(ref) => create(ref as SeamlessFilteredTabPreviewsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
searchPartition: searchPartition,
|
||||
containerFilter: containerFilter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<EquatableCollection<List<TabPreview>>>
|
||||
createElement() {
|
||||
return _SeamlessFilteredTabPreviewsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SeamlessFilteredTabPreviewsProvider &&
|
||||
other.searchPartition == searchPartition &&
|
||||
other.containerFilter == containerFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, searchPartition.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SeamlessFilteredTabPreviewsRef
|
||||
on AutoDisposeProviderRef<EquatableCollection<List<TabPreview>>> {
|
||||
/// The parameter `searchPartition` of this provider.
|
||||
TabSearchPartition get searchPartition;
|
||||
|
||||
/// The parameter `containerFilter` of this provider.
|
||||
ContainerFilter get containerFilter;
|
||||
}
|
||||
|
||||
class _SeamlessFilteredTabPreviewsProviderElement
|
||||
extends AutoDisposeProviderElement<EquatableCollection<List<TabPreview>>>
|
||||
with SeamlessFilteredTabPreviewsRef {
|
||||
_SeamlessFilteredTabPreviewsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
TabSearchPartition get searchPartition =>
|
||||
(origin as SeamlessFilteredTabPreviewsProvider).searchPartition;
|
||||
@override
|
||||
ContainerFilter get containerFilter =>
|
||||
(origin as SeamlessFilteredTabPreviewsProvider).containerFilter;
|
||||
}
|
||||
|
||||
String _$selectedBangTriggerHash() =>
|
||||
|
||||
@@ -48,6 +48,8 @@ class CreateTabStream extends _$CreateTabStream {
|
||||
}
|
||||
|
||||
void createTab(CreateTabSheet parameter) {
|
||||
_streamController.add(parameter);
|
||||
if (!_streamController.isClosed) {
|
||||
_streamController.add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'create_tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$createTabStreamHash() => r'05ad914e41e0d346e082e4638afe5fe5a04052f1';
|
||||
String _$createTabStreamHash() => r'2e802a10ecaf1862074f32b76e89c26997bdd7b2';
|
||||
|
||||
/// See also [CreateTabStream].
|
||||
@ProviderFor(CreateTabStream)
|
||||
|
||||
+18
-19
@@ -7,15 +7,13 @@ import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/providers.dart';
|
||||
import 'package:lensai/data/models/drag_data.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.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/dialogs/tab_action.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';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/entities/container_filter.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
@@ -94,7 +92,9 @@ class _Tab extends HookConsumerWidget {
|
||||
searchTextController,
|
||||
() async {
|
||||
await ref
|
||||
.read(tabSearchRepositoryProvider.notifier)
|
||||
.read(
|
||||
tabSearchRepositoryProvider(TabSearchPartition.preview).notifier,
|
||||
)
|
||||
.addQuery(searchTextController.text);
|
||||
},
|
||||
);
|
||||
@@ -248,14 +248,12 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
builder: (context, ref, child) {
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
|
||||
final filteredTabs = ref
|
||||
.watch(
|
||||
seamlessFilteredTabsProvider(container).select(
|
||||
(value) =>
|
||||
EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
final filteredTabIds = ref.watch(
|
||||
seamlessFilteredTabIdsProvider(
|
||||
TabSearchPartition.preview,
|
||||
ContainerFilterById(containerId: container),
|
||||
),
|
||||
);
|
||||
|
||||
final activeTab = ref.watch(selectedTabProvider);
|
||||
|
||||
@@ -273,7 +271,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final index = filteredTabs
|
||||
final index = filteredTabIds.collection
|
||||
.indexWhere((webView) => webView == activeTab);
|
||||
|
||||
if (index > -1) {
|
||||
@@ -292,12 +290,12 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
|
||||
return null;
|
||||
},
|
||||
[filteredTabs, activeTab],
|
||||
[filteredTabIds, activeTab],
|
||||
);
|
||||
|
||||
final tabs = useMemoized(
|
||||
() {
|
||||
return filteredTabs
|
||||
return filteredTabIds.collection
|
||||
.mapIndexed(
|
||||
(index, tabId) => CustomDraggable(
|
||||
key: Key(tabId),
|
||||
@@ -350,7 +348,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
.toList();
|
||||
},
|
||||
[
|
||||
EquatableCollection(filteredTabs, immutable: true),
|
||||
filteredTabIds,
|
||||
activeTab,
|
||||
],
|
||||
);
|
||||
@@ -375,7 +373,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
final tabId = filteredTabs[oldIndex];
|
||||
final tabId = filteredTabIds.collection[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
@@ -384,13 +382,14 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= filteredTabs.length - 1) {
|
||||
} else if (newIndex >=
|
||||
filteredTabIds.collection.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex;
|
||||
key = await containerRepository.getOrderKeyAfterTab(
|
||||
filteredTabs[orderAfterIndex],
|
||||
filteredTabIds.collection[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:lensai/domain/entities/equatable_image.dart';
|
||||
|
||||
class TabPreview with FastEquatable {
|
||||
final String id;
|
||||
|
||||
final String title;
|
||||
final EquatableImage? icon;
|
||||
|
||||
final Uri url;
|
||||
final String? highlightedUrl;
|
||||
|
||||
final String? content;
|
||||
|
||||
TabPreview({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.url,
|
||||
required this.highlightedUrl,
|
||||
required this.content,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
id,
|
||||
title,
|
||||
icon,
|
||||
url,
|
||||
highlightedUrl,
|
||||
content,
|
||||
];
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'engine_suggestions.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class EngineSuggestions extends _$EngineSuggestions {
|
||||
Future<String?> getAutocompleteSuggestion(String query) {
|
||||
return ref
|
||||
.read(engineSuggestionsServiceProvider)
|
||||
.getAutocompleteSuggestion(query)
|
||||
.then((result) => result?.text);
|
||||
}
|
||||
|
||||
Future<void> addQuery(
|
||||
String query, {
|
||||
List<GeckoSuggestionType> providers = const [
|
||||
GeckoSuggestionType.history,
|
||||
],
|
||||
}) {
|
||||
return ref
|
||||
.read(engineSuggestionsServiceProvider)
|
||||
.querySuggestions(query, providers: providers);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<GeckoSuggestion>> build() {
|
||||
final service = ref.watch(engineSuggestionsServiceProvider);
|
||||
return ConcatStream([Stream.value([]), service.suggestionsStream]);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
AsyncValue<List<GeckoSuggestion>> engineHistorySuggestions(Ref ref) {
|
||||
return ref.watch(
|
||||
engineSuggestionsProvider.select(
|
||||
(suggestions) => suggestions.whenData(
|
||||
(suggestions) => suggestions
|
||||
.where(
|
||||
(suggestion) =>
|
||||
suggestion.type == GeckoSuggestionType.history &&
|
||||
(suggestion.title?.isNotEmpty ?? false) &&
|
||||
(suggestion.description
|
||||
.mapNotNull((url) => Uri.tryParse(url)) !=
|
||||
null),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'engine_suggestions.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$engineHistorySuggestionsHash() =>
|
||||
r'218f1da2140a76c3b02634619955b1382c6ef182';
|
||||
|
||||
/// See also [engineHistorySuggestions].
|
||||
@ProviderFor(engineHistorySuggestions)
|
||||
final engineHistorySuggestionsProvider =
|
||||
AutoDisposeProvider<AsyncValue<List<GeckoSuggestion>>>.internal(
|
||||
engineHistorySuggestions,
|
||||
name: r'engineHistorySuggestionsProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$engineHistorySuggestionsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef EngineHistorySuggestionsRef
|
||||
= AutoDisposeProviderRef<AsyncValue<List<GeckoSuggestion>>>;
|
||||
String _$engineSuggestionsHash() => r'ba2c2d7f0a5e99fd9e639acd020b8cf3016c6126';
|
||||
|
||||
/// See also [EngineSuggestions].
|
||||
@ProviderFor(EngineSuggestions)
|
||||
final engineSuggestionsProvider = AutoDisposeStreamNotifierProvider<
|
||||
EngineSuggestions, List<GeckoSuggestion>>.internal(
|
||||
EngineSuggestions.new,
|
||||
name: r'engineSuggestionsProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$engineSuggestionsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$EngineSuggestions = AutoDisposeStreamNotifier<List<GeckoSuggestion>>;
|
||||
// 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
@@ -11,14 +11,14 @@ ISearchSuggestionProvider defaultSearchSuggestions(Ref ref) {
|
||||
return ref.watch(kagiAutosuggestServiceProvider.notifier);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
@Riverpod()
|
||||
class SearchSuggestions extends _$SearchSuggestions {
|
||||
late void Function(String query) _addQueryBinding;
|
||||
|
||||
void addQuery(String query) => _addQueryBinding(query);
|
||||
|
||||
@override
|
||||
Raw<Stream<List<String>>> build({
|
||||
Stream<List<String>> build({
|
||||
ISearchSuggestionProvider? suggestionsProvider,
|
||||
}) {
|
||||
final defaultProvider = ref.watch(defaultSearchSuggestionsProvider);
|
||||
+13
-11
@@ -25,7 +25,7 @@ final defaultSearchSuggestionsProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef DefaultSearchSuggestionsRef = ProviderRef<ISearchSuggestionProvider>;
|
||||
String _$searchSuggestionsHash() => r'ecc378aca325cc6759292a06d8d4d5ace910eb7f';
|
||||
String _$searchSuggestionsHash() => r'8d1fb72cab374491a53f7a814f87af6a26356f42';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -49,10 +49,10 @@ class _SystemHash {
|
||||
}
|
||||
|
||||
abstract class _$SearchSuggestions
|
||||
extends BuildlessNotifier<Raw<Stream<List<String>>>> {
|
||||
extends BuildlessAutoDisposeStreamNotifier<List<String>> {
|
||||
late final ISearchSuggestionProvider? suggestionsProvider;
|
||||
|
||||
Raw<Stream<List<String>>> build({
|
||||
Stream<List<String>> build({
|
||||
ISearchSuggestionProvider? suggestionsProvider,
|
||||
});
|
||||
}
|
||||
@@ -62,7 +62,7 @@ abstract class _$SearchSuggestions
|
||||
const searchSuggestionsProvider = SearchSuggestionsFamily();
|
||||
|
||||
/// See also [SearchSuggestions].
|
||||
class SearchSuggestionsFamily extends Family<Raw<Stream<List<String>>>> {
|
||||
class SearchSuggestionsFamily extends Family<AsyncValue<List<String>>> {
|
||||
/// See also [SearchSuggestions].
|
||||
const SearchSuggestionsFamily();
|
||||
|
||||
@@ -100,8 +100,8 @@ class SearchSuggestionsFamily extends Family<Raw<Stream<List<String>>>> {
|
||||
}
|
||||
|
||||
/// See also [SearchSuggestions].
|
||||
class SearchSuggestionsProvider
|
||||
extends NotifierProviderImpl<SearchSuggestions, Raw<Stream<List<String>>>> {
|
||||
class SearchSuggestionsProvider extends AutoDisposeStreamNotifierProviderImpl<
|
||||
SearchSuggestions, List<String>> {
|
||||
/// See also [SearchSuggestions].
|
||||
SearchSuggestionsProvider({
|
||||
ISearchSuggestionProvider? suggestionsProvider,
|
||||
@@ -132,7 +132,7 @@ class SearchSuggestionsProvider
|
||||
final ISearchSuggestionProvider? suggestionsProvider;
|
||||
|
||||
@override
|
||||
Raw<Stream<List<String>>> runNotifierBuild(
|
||||
Stream<List<String>> runNotifierBuild(
|
||||
covariant SearchSuggestions notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
@@ -157,7 +157,7 @@ class SearchSuggestionsProvider
|
||||
}
|
||||
|
||||
@override
|
||||
NotifierProviderElement<SearchSuggestions, Raw<Stream<List<String>>>>
|
||||
AutoDisposeStreamNotifierProviderElement<SearchSuggestions, List<String>>
|
||||
createElement() {
|
||||
return _SearchSuggestionsProviderElement(this);
|
||||
}
|
||||
@@ -179,13 +179,15 @@ class SearchSuggestionsProvider
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SearchSuggestionsRef on NotifierProviderRef<Raw<Stream<List<String>>>> {
|
||||
mixin SearchSuggestionsRef
|
||||
on AutoDisposeStreamNotifierProviderRef<List<String>> {
|
||||
/// The parameter `suggestionsProvider` of this provider.
|
||||
ISearchSuggestionProvider? get suggestionsProvider;
|
||||
}
|
||||
|
||||
class _SearchSuggestionsProviderElement extends NotifierProviderElement<
|
||||
SearchSuggestions, Raw<Stream<List<String>>>> with SearchSuggestionsRef {
|
||||
class _SearchSuggestionsProviderElement
|
||||
extends AutoDisposeStreamNotifierProviderElement<SearchSuggestions,
|
||||
List<String>> with SearchSuggestionsRef {
|
||||
_SearchSuggestionsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
@@ -0,0 +1,121 @@
|
||||
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/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.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/search/presentation/widgets/search_field.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/tab_search.dart';
|
||||
|
||||
class SearchScreen extends HookConsumerWidget {
|
||||
final String? initialSearchText;
|
||||
|
||||
const SearchScreen({required this.initialSearchText});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
|
||||
final searchTextController =
|
||||
useTextEditingController(text: initialSearchText);
|
||||
final searchFocusNode = useFocusNode();
|
||||
|
||||
final defaultSearchBang = ref.watch(
|
||||
defaultSearchBangDataProvider.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
final selectedBang = useState<BangData?>(null);
|
||||
final activeBang = selectedBang.value ?? defaultSearchBang;
|
||||
final showBangIcon = useState(false);
|
||||
|
||||
ref.listen(
|
||||
selectedBangDataProvider(),
|
||||
(previous, next) {
|
||||
if (next.hasValue) {
|
||||
if ((previous?.hasValue ?? false) && previous!.value != next.value) {
|
||||
showBangIcon.value = true;
|
||||
}
|
||||
|
||||
selectedBang.value = next.value;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> submitSearch(String query) async {
|
||||
if (activeBang != null && (formKey.currentState?.validate() == true)) {
|
||||
final searchUri = await ref.read(
|
||||
triggerBangSearchProvider(activeBang, query).future,
|
||||
);
|
||||
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri);
|
||||
|
||||
if (context.mounted) {
|
||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: Form(
|
||||
key: formKey,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
automaticallyImplyLeading: false,
|
||||
title: SearchField(
|
||||
showBangIcon: showBangIcon.value,
|
||||
textEditingController: searchTextController,
|
||||
focusNode: searchFocusNode,
|
||||
autofocus: true,
|
||||
onSubmitted: (text) async {
|
||||
if (Uri.tryParse(text) case final Uri url) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: url);
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.dismiss();
|
||||
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
activeBang: activeBang,
|
||||
showSuggestions: true,
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: Divider(),
|
||||
),
|
||||
SearchTermSuggestions(
|
||||
searchTextController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
submitSearch: submitSearch,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: Divider(),
|
||||
),
|
||||
TabSearch(
|
||||
searchTextController: searchTextController,
|
||||
),
|
||||
HistorySuggestions(
|
||||
searchTextController: searchTextController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
|
||||
@@ -75,7 +75,8 @@ class BangChips extends HookConsumerWidget {
|
||||
Expanded(
|
||||
child: SelectableChips(
|
||||
itemId: (bang) => bang.trigger,
|
||||
itemAvatar: (bang) => BangIcon(bang, iconSize: 20),
|
||||
itemAvatar: (bang) =>
|
||||
UrlIcon(bang.getUrl(''), iconSize: 20),
|
||||
itemLabel: (bang) => Text(bang.websiteName),
|
||||
availableItems: availableBangs,
|
||||
selectedItem: activeBang,
|
||||
+33
-7
@@ -2,14 +2,19 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/providers/engine_suggestions.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/bang_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
||||
|
||||
class SearchField extends HookConsumerWidget {
|
||||
final TextEditingController textEditingController;
|
||||
final FocusNode? focusNode;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final bool autofocus;
|
||||
final void Function(String)? onSubmitted;
|
||||
final bool showSuggestions;
|
||||
|
||||
final BangData? activeBang;
|
||||
final bool showBangIcon;
|
||||
@@ -17,10 +22,12 @@ class SearchField extends HookConsumerWidget {
|
||||
const SearchField({
|
||||
super.key,
|
||||
required this.textEditingController,
|
||||
required this.onFieldSubmitted,
|
||||
required this.onSubmitted,
|
||||
required this.activeBang,
|
||||
required this.showSuggestions,
|
||||
this.focusNode,
|
||||
this.showBangIcon = true,
|
||||
this.autofocus = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -32,20 +39,39 @@ class SearchField extends HookConsumerWidget {
|
||||
() => textEditingController.text.isNotEmpty,
|
||||
);
|
||||
|
||||
return TextFormField(
|
||||
final suggestion = useState<String?>(null);
|
||||
|
||||
if (showSuggestions) {
|
||||
useListenableCallback(
|
||||
textEditingController,
|
||||
() async {
|
||||
if (textEditingController.text.isNotEmpty) {
|
||||
final result = await ref
|
||||
.read(engineSuggestionsProvider.notifier)
|
||||
.getAutocompleteSuggestion(textEditingController.text);
|
||||
|
||||
suggestion.value = result;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return AutoSuggestTextField(
|
||||
controller: textEditingController,
|
||||
suggestion: suggestion.value,
|
||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
prefixIcon: (showBangIcon && activeBang != null)
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.all(12.0),
|
||||
child: BangIcon(activeBang!, iconSize: 24.0),
|
||||
child: UrlIcon(activeBang!.getUrl(''), iconSize: 24.0),
|
||||
)
|
||||
: null,
|
||||
label: const Text('Search'),
|
||||
hintText: 'Ask anything...',
|
||||
// hintText: 'Ask anything...',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
suffixIcon: hasText
|
||||
? IconButton(
|
||||
@@ -65,7 +91,7 @@ class SearchField extends HookConsumerWidget {
|
||||
focusNode!.unfocus();
|
||||
}
|
||||
: null,
|
||||
onFieldSubmitted: onFieldSubmitted,
|
||||
onSubmitted: onSubmitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
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/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/providers/engine_suggestions.dart';
|
||||
import 'package:lensai/features/geckoview/utils/image_helper.dart';
|
||||
import 'package:lensai/presentation/hooks/cached_future.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
|
||||
class HistorySuggestions extends HookConsumerWidget {
|
||||
final TextEditingController searchTextController;
|
||||
|
||||
const HistorySuggestions({super.key, required this.searchTextController});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final historySuggestions = ref.watch(engineHistorySuggestionsProvider);
|
||||
|
||||
useListenableCallback(
|
||||
searchTextController,
|
||||
() async {
|
||||
await ref
|
||||
.watch(engineSuggestionsProvider.notifier)
|
||||
.addQuery(searchTextController.text);
|
||||
},
|
||||
);
|
||||
|
||||
if (historySuggestions.hasValue &&
|
||||
(historySuggestions.valueOrNull?.isEmpty ?? true)) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
return MultiSliver(
|
||||
children: [
|
||||
const SliverToBoxAdapter(
|
||||
child: Divider(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Text(
|
||||
'History',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverSkeletonizer(
|
||||
enabled: historySuggestions.isLoading,
|
||||
child: historySuggestions.when(
|
||||
data: (historySuggestions) {
|
||||
return SliverList.builder(
|
||||
itemCount: historySuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = historySuggestions[index];
|
||||
|
||||
return HookBuilder(
|
||||
key: ValueKey(suggestion.id),
|
||||
builder: (context) {
|
||||
final icon = useCachedFuture(
|
||||
() async => suggestion.icon.mapNotNull(tryDecodeImage),
|
||||
//URL as key
|
||||
[suggestion.description],
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
leading: RepaintBoundary(
|
||||
child: RawImage(
|
||||
image: icon.data,
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
),
|
||||
title:
|
||||
suggestion.title.mapNotNull((title) => Text(title)),
|
||||
subtitle: suggestion.description.mapNotNull(
|
||||
(description) => Text(
|
||||
description,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
if (suggestion.description != null) {
|
||||
if (Uri.tryParse(suggestion.description!)
|
||||
case final Uri url) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: url);
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(
|
||||
bottomSheetControllerProvider.notifier,
|
||||
)
|
||||
.dismiss();
|
||||
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) {
|
||||
return FailureWidget(
|
||||
title: 'Could not load history',
|
||||
exception: error,
|
||||
);
|
||||
},
|
||||
loading: () => SliverList.builder(
|
||||
itemCount: historySuggestions.valueOrNull?.length ?? 3,
|
||||
itemBuilder: (context, index) {
|
||||
return const ListTile(
|
||||
title: Bone.text(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/domain/providers/search_suggestions.dart';
|
||||
import 'package:lensai/features/geckoview/features/search/presentation/widgets/bang_chips.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
|
||||
class SearchTermSuggestions extends HookConsumerWidget {
|
||||
final TextEditingController searchTextController;
|
||||
final Future<void> Function(String query) submitSearch;
|
||||
final BangData? activeBang;
|
||||
|
||||
final bool showHistory;
|
||||
final bool showChips;
|
||||
|
||||
const SearchTermSuggestions({
|
||||
required this.searchTextController,
|
||||
required this.submitSearch,
|
||||
required this.activeBang,
|
||||
this.showHistory = true,
|
||||
this.showChips = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchTextIsNotEmpty = useListenableSelector(
|
||||
searchTextController,
|
||||
() => searchTextController.text.isNotEmpty,
|
||||
);
|
||||
|
||||
final searchSuggestions = ref.watch(searchSuggestionsProvider());
|
||||
|
||||
final searchHistory = ref.watch(searchHistoryProvider);
|
||||
|
||||
useListenableCallback(
|
||||
searchTextController,
|
||||
() async {
|
||||
ref
|
||||
.read(searchSuggestionsProvider().notifier)
|
||||
.addQuery(searchTextController.text);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget listSliver;
|
||||
|
||||
if (showHistory &&
|
||||
!searchTextIsNotEmpty &&
|
||||
(searchHistory.value?.isNotEmpty ?? false)) {
|
||||
final entries = searchHistory.value!;
|
||||
|
||||
listSliver = SliverList.builder(
|
||||
itemCount: entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final query = entries[index].searchQuery;
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: Text(query),
|
||||
onLongPress: () {
|
||||
searchTextController.text = query;
|
||||
},
|
||||
onTap: () async {
|
||||
await submitSearch(query);
|
||||
},
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.removeSearchEntry(query);
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
final prioritizedSuggestions = [
|
||||
if (searchTextIsNotEmpty) searchTextController.text,
|
||||
if (searchSuggestions.valueOrNull != null)
|
||||
...searchSuggestions.valueOrNull!.whereNot(
|
||||
(suggestion) => suggestion == searchTextController.text,
|
||||
),
|
||||
];
|
||||
|
||||
listSliver = SliverList.builder(
|
||||
itemCount: prioritizedSuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = prioritizedSuggestions[index];
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.search),
|
||||
title: Text(suggestion),
|
||||
onLongPress: () {
|
||||
searchTextController.text = suggestion;
|
||||
},
|
||||
onTap: () async {
|
||||
await submitSearch(suggestion);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return MultiSliver(
|
||||
children: [
|
||||
if (showChips)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Search Provider',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
BangChips(
|
||||
activeBang: activeBang,
|
||||
onSelected: (bang) {
|
||||
searchTextController.clear();
|
||||
|
||||
ref
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(bang.trigger);
|
||||
},
|
||||
onDeleted: (bang) async {
|
||||
if (ref.read(selectedBangTriggerProvider()) ==
|
||||
bang.trigger) {
|
||||
ref
|
||||
.read(
|
||||
selectedBangTriggerProvider().notifier,
|
||||
)
|
||||
.clearTrigger();
|
||||
} else {
|
||||
final dialogResult = await BangChips.resetBangDialog(
|
||||
context,
|
||||
bang.trigger,
|
||||
);
|
||||
|
||||
if (dialogResult == true) {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.resetFrequency(bang.trigger);
|
||||
}
|
||||
}
|
||||
},
|
||||
searchTextController: searchTextController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 150),
|
||||
child: FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
controller: controller,
|
||||
slivers: [
|
||||
listSliver,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.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/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.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/tabs/data/entities/container_filter.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/bang_icon.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
|
||||
class TabSearch extends HookConsumerWidget {
|
||||
static const _matchPrefix = '***';
|
||||
static const _matchSuffix = '***';
|
||||
|
||||
final TextEditingController searchTextController;
|
||||
|
||||
const TabSearch({required this.searchTextController});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedContainer = useState<ContainerData?>(
|
||||
ref.read(
|
||||
selectedContainerDataProvider.select((value) => value.valueOrNull),
|
||||
),
|
||||
);
|
||||
|
||||
final tabs = ref
|
||||
.watch(
|
||||
seamlessFilteredTabPreviewsProvider(
|
||||
TabSearchPartition.search,
|
||||
(selectedContainer.value != null)
|
||||
? ContainerFilterById(containerId: selectedContainer.value!.id)
|
||||
: ContainerFilterDisabled(),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
useListenableCallback(
|
||||
searchTextController,
|
||||
() async {
|
||||
await ref
|
||||
.read(
|
||||
tabSearchRepositoryProvider(TabSearchPartition.search).notifier,
|
||||
)
|
||||
.addQuery(
|
||||
searchTextController.text,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
matchPrefix: _matchPrefix,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
matchSuffix: _matchSuffix,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return MultiSliver(
|
||||
children: [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Tabs',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
ContainerChips(
|
||||
displayMenu: false,
|
||||
selectedContainer: selectedContainer.value,
|
||||
onSelected: (container) {
|
||||
selectedContainer.value = container;
|
||||
},
|
||||
onDeleted: (container) {
|
||||
selectedContainer.value = null;
|
||||
},
|
||||
containerFilter: (container) => (container.tabCount ?? 0) > 0,
|
||||
searchTextController: searchTextController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: tabs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final result = tabs[index];
|
||||
|
||||
final urlHasMatch =
|
||||
result.highlightedUrl?.contains(_matchPrefix) ?? false;
|
||||
final bodyHasMatch =
|
||||
result.content?.contains(_matchPrefix) ?? false;
|
||||
|
||||
return ListTile(
|
||||
leading: RepaintBoundary(
|
||||
child: (result.icon != null)
|
||||
? RawImage(
|
||||
image: result.icon?.value,
|
||||
height: 24,
|
||||
width: 24,
|
||||
)
|
||||
: UrlIcon(result.url, iconSize: 24),
|
||||
),
|
||||
title: result.title.mapNotNull(
|
||||
(title) => MarkdownBody(
|
||||
data: title,
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
p: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: MarkdownBody(
|
||||
data: (bodyHasMatch && !urlHasMatch)
|
||||
? result.content!
|
||||
: result.highlightedUrl ?? result.url.toString(),
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectTab(result.id);
|
||||
|
||||
if (context.mounted) {
|
||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,8 @@ queryTabsBasic WITH TabQueryResult:
|
||||
t.id,
|
||||
highlight(tab_fts, 0, :beforeMatch, :afterMatch) AS title,
|
||||
highlight(tab_fts, 1, :beforeMatch, :afterMatch) AS url,
|
||||
bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank
|
||||
bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank,
|
||||
t.url AS clean_url
|
||||
FROM tab_fts fts
|
||||
INNER JOIN
|
||||
tab t ON t.rowid = fts.rowid
|
||||
@@ -157,7 +158,8 @@ queryTabsFullContent WITH TabQueryResult:
|
||||
(
|
||||
bm25(tab_fts, weights.title_weight, weights.url_weight,
|
||||
weights.extracted_weight, weights.full_weight)
|
||||
) AS weighted_rank
|
||||
) AS weighted_rank,
|
||||
t.url AS clean_url
|
||||
FROM tab_fts(:query) fts
|
||||
INNER JOIN
|
||||
tab t ON t.rowid = fts.rowid
|
||||
|
||||
@@ -1639,7 +1639,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
required String afterMatch,
|
||||
required String query}) {
|
||||
return customSelect(
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight) SELECT t.id, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank FROM tab_fts AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights WHERE fts.title LIKE ?3 OR fts.url LIKE ?3 ORDER BY weighted_rank ASC, t.timestamp DESC',
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight) SELECT t.id, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank, t.url AS clean_url FROM tab_fts AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights WHERE fts.title LIKE ?3 OR fts.url LIKE ?3 ORDER BY weighted_rank ASC, t.timestamp DESC',
|
||||
variables: [
|
||||
Variable<String>(beforeMatch),
|
||||
Variable<String>(afterMatch),
|
||||
@@ -1652,6 +1652,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
id: row.read<String>('id'),
|
||||
title: row.readNullable<String>('title'),
|
||||
url: row.readNullable<String>('url'),
|
||||
cleanUrl: row.readNullable<String>('clean_url'),
|
||||
weightedRank: row.read<double>('weighted_rank'),
|
||||
));
|
||||
}
|
||||
@@ -1663,7 +1664,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
required int snippetLength,
|
||||
required String query}) {
|
||||
return customSelect(
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC',
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank, t.url AS clean_url FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC',
|
||||
variables: [
|
||||
Variable<String>(beforeMatch),
|
||||
Variable<String>(afterMatch),
|
||||
@@ -1678,6 +1679,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
id: row.read<String>('id'),
|
||||
title: row.readNullable<String>('title'),
|
||||
url: row.readNullable<String>('url'),
|
||||
cleanUrl: row.readNullable<String>('clean_url'),
|
||||
extractedContent: row.readNullable<String>('extracted_content'),
|
||||
fullContent: row.readNullable<String>('full_content'),
|
||||
weightedRank: row.read<double>('weighted_rank'),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
|
||||
sealed class ContainerFilter with FastEquatable {}
|
||||
|
||||
class ContainerFilterById extends ContainerFilter {
|
||||
final String? containerId;
|
||||
|
||||
ContainerFilterById({required this.containerId});
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [containerId];
|
||||
}
|
||||
|
||||
class ContainerFilterDisabled extends ContainerFilter {
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [null];
|
||||
}
|
||||
@@ -2,8 +2,9 @@ class TabQueryResult {
|
||||
final String id;
|
||||
|
||||
final String? title;
|
||||
final String? url;
|
||||
final String? cleanUrl;
|
||||
|
||||
final String? url;
|
||||
final String? extractedContent;
|
||||
final String? fullContent;
|
||||
|
||||
@@ -13,6 +14,7 @@ class TabQueryResult {
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.url,
|
||||
required this.cleanUrl,
|
||||
this.extractedContent,
|
||||
this.fullContent,
|
||||
required this.weightedRank,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/entities/container_filter.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
@@ -37,7 +38,7 @@ Stream<List<ContainerDataWithCount>> containersWithCount(
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
AsyncValue<List<ContainerDataWithCount>> filteredContainersWithCount(
|
||||
AsyncValue<List<ContainerDataWithCount>> matchSortedContainersWithCount(
|
||||
Ref ref,
|
||||
String? searchText,
|
||||
) {
|
||||
@@ -48,7 +49,7 @@ AsyncValue<List<ContainerDataWithCount>> filteredContainersWithCount(
|
||||
}
|
||||
|
||||
return value.whenData(
|
||||
(cb) => TokenizedFilter(
|
||||
(cb) => TokenizedFilter.sort(
|
||||
items: cb,
|
||||
toString: (item) => item.name,
|
||||
query: searchText!,
|
||||
@@ -61,8 +62,14 @@ AsyncValue<List<ContainerDataWithCount>> filteredContainersWithCount(
|
||||
@Riverpod()
|
||||
Stream<List<String>> containerTabIds(
|
||||
Ref ref,
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.tabDao.containerTabIds(containerId).watch();
|
||||
|
||||
switch (containerFilter) {
|
||||
case ContainerFilterById(:final containerId):
|
||||
return db.tabDao.containerTabIds(containerId).watch();
|
||||
case ContainerFilterDisabled():
|
||||
return db.tabDao.allTabIds().watch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ final containersWithCountProvider =
|
||||
// ignore: unused_element
|
||||
typedef ContainersWithCountRef
|
||||
= AutoDisposeStreamProviderRef<List<ContainerDataWithCount>>;
|
||||
String _$filteredContainersWithCountHash() =>
|
||||
r'3a7284df29182a32f9b7831867a3a0b3e171b382';
|
||||
String _$matchSortedContainersWithCountHash() =>
|
||||
r'0ed555f0cb72f4990a2841114d72f57d8178d5c3';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -69,28 +69,29 @@ class _SystemHash {
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [filteredContainersWithCount].
|
||||
@ProviderFor(filteredContainersWithCount)
|
||||
const filteredContainersWithCountProvider = FilteredContainersWithCountFamily();
|
||||
/// See also [matchSortedContainersWithCount].
|
||||
@ProviderFor(matchSortedContainersWithCount)
|
||||
const matchSortedContainersWithCountProvider =
|
||||
MatchSortedContainersWithCountFamily();
|
||||
|
||||
/// See also [filteredContainersWithCount].
|
||||
class FilteredContainersWithCountFamily
|
||||
/// See also [matchSortedContainersWithCount].
|
||||
class MatchSortedContainersWithCountFamily
|
||||
extends Family<AsyncValue<List<ContainerDataWithCount>>> {
|
||||
/// See also [filteredContainersWithCount].
|
||||
const FilteredContainersWithCountFamily();
|
||||
/// See also [matchSortedContainersWithCount].
|
||||
const MatchSortedContainersWithCountFamily();
|
||||
|
||||
/// See also [filteredContainersWithCount].
|
||||
FilteredContainersWithCountProvider call(
|
||||
/// See also [matchSortedContainersWithCount].
|
||||
MatchSortedContainersWithCountProvider call(
|
||||
String? searchText,
|
||||
) {
|
||||
return FilteredContainersWithCountProvider(
|
||||
return MatchSortedContainersWithCountProvider(
|
||||
searchText,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FilteredContainersWithCountProvider getProviderOverride(
|
||||
covariant FilteredContainersWithCountProvider provider,
|
||||
MatchSortedContainersWithCountProvider getProviderOverride(
|
||||
covariant MatchSortedContainersWithCountProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.searchText,
|
||||
@@ -109,33 +110,33 @@ class FilteredContainersWithCountFamily
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'filteredContainersWithCountProvider';
|
||||
String? get name => r'matchSortedContainersWithCountProvider';
|
||||
}
|
||||
|
||||
/// See also [filteredContainersWithCount].
|
||||
class FilteredContainersWithCountProvider
|
||||
/// See also [matchSortedContainersWithCount].
|
||||
class MatchSortedContainersWithCountProvider
|
||||
extends AutoDisposeProvider<AsyncValue<List<ContainerDataWithCount>>> {
|
||||
/// See also [filteredContainersWithCount].
|
||||
FilteredContainersWithCountProvider(
|
||||
/// See also [matchSortedContainersWithCount].
|
||||
MatchSortedContainersWithCountProvider(
|
||||
String? searchText,
|
||||
) : this._internal(
|
||||
(ref) => filteredContainersWithCount(
|
||||
ref as FilteredContainersWithCountRef,
|
||||
(ref) => matchSortedContainersWithCount(
|
||||
ref as MatchSortedContainersWithCountRef,
|
||||
searchText,
|
||||
),
|
||||
from: filteredContainersWithCountProvider,
|
||||
name: r'filteredContainersWithCountProvider',
|
||||
from: matchSortedContainersWithCountProvider,
|
||||
name: r'matchSortedContainersWithCountProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$filteredContainersWithCountHash,
|
||||
dependencies: FilteredContainersWithCountFamily._dependencies,
|
||||
: _$matchSortedContainersWithCountHash,
|
||||
dependencies: MatchSortedContainersWithCountFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
FilteredContainersWithCountFamily._allTransitiveDependencies,
|
||||
MatchSortedContainersWithCountFamily._allTransitiveDependencies,
|
||||
searchText: searchText,
|
||||
);
|
||||
|
||||
FilteredContainersWithCountProvider._internal(
|
||||
MatchSortedContainersWithCountProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
@@ -150,13 +151,13 @@ class FilteredContainersWithCountProvider
|
||||
@override
|
||||
Override overrideWith(
|
||||
AsyncValue<List<ContainerDataWithCount>> Function(
|
||||
FilteredContainersWithCountRef provider)
|
||||
MatchSortedContainersWithCountRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: FilteredContainersWithCountProvider._internal(
|
||||
(ref) => create(ref as FilteredContainersWithCountRef),
|
||||
override: MatchSortedContainersWithCountProvider._internal(
|
||||
(ref) => create(ref as MatchSortedContainersWithCountRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
@@ -170,12 +171,12 @@ class FilteredContainersWithCountProvider
|
||||
@override
|
||||
AutoDisposeProviderElement<AsyncValue<List<ContainerDataWithCount>>>
|
||||
createElement() {
|
||||
return _FilteredContainersWithCountProviderElement(this);
|
||||
return _MatchSortedContainersWithCountProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FilteredContainersWithCountProvider &&
|
||||
return other is MatchSortedContainersWithCountProvider &&
|
||||
other.searchText == searchText;
|
||||
}
|
||||
|
||||
@@ -190,23 +191,23 @@ class FilteredContainersWithCountProvider
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin FilteredContainersWithCountRef
|
||||
mixin MatchSortedContainersWithCountRef
|
||||
on AutoDisposeProviderRef<AsyncValue<List<ContainerDataWithCount>>> {
|
||||
/// The parameter `searchText` of this provider.
|
||||
String? get searchText;
|
||||
}
|
||||
|
||||
class _FilteredContainersWithCountProviderElement
|
||||
class _MatchSortedContainersWithCountProviderElement
|
||||
extends AutoDisposeProviderElement<AsyncValue<List<ContainerDataWithCount>>>
|
||||
with FilteredContainersWithCountRef {
|
||||
_FilteredContainersWithCountProviderElement(super.provider);
|
||||
with MatchSortedContainersWithCountRef {
|
||||
_MatchSortedContainersWithCountProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get searchText =>
|
||||
(origin as FilteredContainersWithCountProvider).searchText;
|
||||
(origin as MatchSortedContainersWithCountProvider).searchText;
|
||||
}
|
||||
|
||||
String _$containerTabIdsHash() => r'7545a6c500b1832bf81c0838e257dfe5e051463d';
|
||||
String _$containerTabIdsHash() => r'6472a865c287d7f87ec49a401c8184bfe7528ab8';
|
||||
|
||||
/// See also [containerTabIds].
|
||||
@ProviderFor(containerTabIds)
|
||||
@@ -219,10 +220,10 @@ class ContainerTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
|
||||
/// See also [containerTabIds].
|
||||
ContainerTabIdsProvider call(
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) {
|
||||
return ContainerTabIdsProvider(
|
||||
containerId,
|
||||
containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -231,7 +232,7 @@ class ContainerTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
covariant ContainerTabIdsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.containerId,
|
||||
provider.containerFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,11 +255,11 @@ class ContainerTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
/// See also [containerTabIds].
|
||||
ContainerTabIdsProvider(
|
||||
String? containerId,
|
||||
ContainerFilter containerFilter,
|
||||
) : this._internal(
|
||||
(ref) => containerTabIds(
|
||||
ref as ContainerTabIdsRef,
|
||||
containerId,
|
||||
containerFilter,
|
||||
),
|
||||
from: containerTabIdsProvider,
|
||||
name: r'containerTabIdsProvider',
|
||||
@@ -269,7 +270,7 @@ class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
dependencies: ContainerTabIdsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ContainerTabIdsFamily._allTransitiveDependencies,
|
||||
containerId: containerId,
|
||||
containerFilter: containerFilter,
|
||||
);
|
||||
|
||||
ContainerTabIdsProvider._internal(
|
||||
@@ -279,10 +280,10 @@ class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.containerId,
|
||||
required this.containerFilter,
|
||||
}) : super.internal();
|
||||
|
||||
final String? containerId;
|
||||
final ContainerFilter containerFilter;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
@@ -297,7 +298,7 @@ class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
containerId: containerId,
|
||||
containerFilter: containerFilter,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -309,13 +310,14 @@ class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ContainerTabIdsProvider && other.containerId == containerId;
|
||||
return other is ContainerTabIdsProvider &&
|
||||
other.containerFilter == containerFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerFilter.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -324,8 +326,8 @@ class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ContainerTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
/// The parameter `containerFilter` of this provider.
|
||||
ContainerFilter get containerFilter;
|
||||
}
|
||||
|
||||
class _ContainerTabIdsProviderElement
|
||||
@@ -334,7 +336,8 @@ class _ContainerTabIdsProviderElement
|
||||
_ContainerTabIdsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get containerId => (origin as ContainerTabIdsProvider).containerId;
|
||||
ContainerFilter get containerFilter =>
|
||||
(origin as ContainerTabIdsProvider).containerFilter;
|
||||
}
|
||||
// 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
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tab_search.g.dart';
|
||||
|
||||
enum TabSearchPartition { preview, search }
|
||||
|
||||
@Riverpod()
|
||||
class TabSearchRepository extends _$TabSearchRepository {
|
||||
Future<void> addQuery(
|
||||
@@ -35,7 +37,7 @@ class TabSearchRepository extends _$TabSearchRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TabQueryResult>?> build() {
|
||||
Future<List<TabQueryResult>?> build(TabSearchPartition partition) {
|
||||
return Future.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,174 @@ part of 'tab_search.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabSearchRepositoryHash() =>
|
||||
r'57d130f50aa3feb7a31d27f9a5b54234e0e7064f';
|
||||
r'd442c384a5a867f4ff28465f524a6ef67521d5ae';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$TabSearchRepository
|
||||
extends BuildlessAutoDisposeAsyncNotifier<List<TabQueryResult>?> {
|
||||
late final TabSearchPartition partition;
|
||||
|
||||
FutureOr<List<TabQueryResult>?> build(
|
||||
TabSearchPartition partition,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [TabSearchRepository].
|
||||
@ProviderFor(TabSearchRepository)
|
||||
final tabSearchRepositoryProvider = AutoDisposeAsyncNotifierProvider<
|
||||
TabSearchRepository, List<TabQueryResult>?>.internal(
|
||||
TabSearchRepository.new,
|
||||
name: r'tabSearchRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$tabSearchRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
const tabSearchRepositoryProvider = TabSearchRepositoryFamily();
|
||||
|
||||
typedef _$TabSearchRepository = AutoDisposeAsyncNotifier<List<TabQueryResult>?>;
|
||||
/// See also [TabSearchRepository].
|
||||
class TabSearchRepositoryFamily
|
||||
extends Family<AsyncValue<List<TabQueryResult>?>> {
|
||||
/// See also [TabSearchRepository].
|
||||
const TabSearchRepositoryFamily();
|
||||
|
||||
/// See also [TabSearchRepository].
|
||||
TabSearchRepositoryProvider call(
|
||||
TabSearchPartition partition,
|
||||
) {
|
||||
return TabSearchRepositoryProvider(
|
||||
partition,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TabSearchRepositoryProvider getProviderOverride(
|
||||
covariant TabSearchRepositoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.partition,
|
||||
);
|
||||
}
|
||||
|
||||
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'tabSearchRepositoryProvider';
|
||||
}
|
||||
|
||||
/// See also [TabSearchRepository].
|
||||
class TabSearchRepositoryProvider extends AutoDisposeAsyncNotifierProviderImpl<
|
||||
TabSearchRepository, List<TabQueryResult>?> {
|
||||
/// See also [TabSearchRepository].
|
||||
TabSearchRepositoryProvider(
|
||||
TabSearchPartition partition,
|
||||
) : this._internal(
|
||||
() => TabSearchRepository()..partition = partition,
|
||||
from: tabSearchRepositoryProvider,
|
||||
name: r'tabSearchRepositoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$tabSearchRepositoryHash,
|
||||
dependencies: TabSearchRepositoryFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TabSearchRepositoryFamily._allTransitiveDependencies,
|
||||
partition: partition,
|
||||
);
|
||||
|
||||
TabSearchRepositoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.partition,
|
||||
}) : super.internal();
|
||||
|
||||
final TabSearchPartition partition;
|
||||
|
||||
@override
|
||||
FutureOr<List<TabQueryResult>?> runNotifierBuild(
|
||||
covariant TabSearchRepository notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
partition,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(TabSearchRepository Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TabSearchRepositoryProvider._internal(
|
||||
() => create()..partition = partition,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
partition: partition,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeAsyncNotifierProviderElement<TabSearchRepository,
|
||||
List<TabQueryResult>?> createElement() {
|
||||
return _TabSearchRepositoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabSearchRepositoryProvider && other.partition == partition;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, partition.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin TabSearchRepositoryRef
|
||||
on AutoDisposeAsyncNotifierProviderRef<List<TabQueryResult>?> {
|
||||
/// The parameter `partition` of this provider.
|
||||
TabSearchPartition get partition;
|
||||
}
|
||||
|
||||
class _TabSearchRepositoryProviderElement
|
||||
extends AutoDisposeAsyncNotifierProviderElement<TabSearchRepository,
|
||||
List<TabQueryResult>?> with TabSearchRepositoryRef {
|
||||
_TabSearchRepositoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
TabSearchPartition get partition =>
|
||||
(origin as TabSearchRepositoryProvider).partition;
|
||||
}
|
||||
// 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
|
||||
|
||||
@@ -18,6 +18,7 @@ class ContainerChips extends HookConsumerWidget {
|
||||
final bool displayMenu;
|
||||
|
||||
final ContainerData? selectedContainer;
|
||||
final bool Function(ContainerDataWithCount)? containerFilter;
|
||||
final void Function(ContainerDataWithCount)? onSelected;
|
||||
final void Function(ContainerDataWithCount)? onDeleted;
|
||||
|
||||
@@ -27,6 +28,7 @@ class ContainerChips extends HookConsumerWidget {
|
||||
required this.selectedContainer,
|
||||
required this.onSelected,
|
||||
required this.onDeleted,
|
||||
this.containerFilter,
|
||||
this.searchTextController,
|
||||
this.displayMenu = true,
|
||||
});
|
||||
@@ -39,12 +41,16 @@ class ContainerChips extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
final containersAsync =
|
||||
ref.watch(filteredContainersWithCountProvider(searchText));
|
||||
ref.watch(matchSortedContainersWithCountProvider(searchText));
|
||||
|
||||
final dragTargetTabId = useValueNotifier<String?>(null);
|
||||
|
||||
return containersAsync.when(
|
||||
data: (availableContainers) {
|
||||
data: (containers) {
|
||||
final availableContainers = containerFilter
|
||||
.mapNotNull((filter) => containers.where(filter).toList()) ??
|
||||
containers;
|
||||
|
||||
if (selectedContainer == null &&
|
||||
availableContainers.isEmpty &&
|
||||
!displayMenu) {
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:lensai/utils/lru_cache.dart';
|
||||
import 'package:xxh3/xxh3.dart';
|
||||
|
||||
final _cache = LRUCache<int, Image>(100);
|
||||
|
||||
Future<Image?> tryDecodeImage(
|
||||
Uint8List bytes, {
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
bool allowUpscaling = true,
|
||||
}) async {
|
||||
final digest = xxh3(bytes);
|
||||
|
||||
final cached = _cache.get(digest);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
final codec = await instantiateImageCodec(
|
||||
bytes,
|
||||
@@ -19,6 +31,7 @@ Future<Image?> tryDecodeImage(
|
||||
final image = frameInfo.image;
|
||||
|
||||
if (image.width > 0) {
|
||||
_cache.set(digest, image);
|
||||
return image;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -16,7 +16,9 @@ class SearchSuggestionsRepository extends _$SearchSuggestionsRepository {
|
||||
SearchSuggestionsRepository() : _cache = LRUCache(100);
|
||||
|
||||
void addQuery(String query) {
|
||||
_queryStreamController.add(query);
|
||||
if (!_queryStreamController.isClosed) {
|
||||
_queryStreamController.add(query);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'search_suggestions.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$searchSuggestionsRepositoryHash() =>
|
||||
r'bc6dc862ae7d887b404972abbbee3c1b27a64d2e';
|
||||
r'be75d075c3479c3d5e462211dec8bbd21b106820';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.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/data/models/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/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.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/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||
import 'package:lensai/features/search/domain/providers/search_suggestions.dart';
|
||||
import 'package:lensai/features/search/presentation/widgets/bang_chips.dart';
|
||||
import 'package:lensai/features/search/presentation/widgets/search_field.dart';
|
||||
import 'package:lensai/features/search/presentation/widgets/search_suggestion_list.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class SearchScreen extends HookConsumerWidget {
|
||||
static const _matchPrefix = '***';
|
||||
|
||||
final String? initialSearchText;
|
||||
|
||||
const SearchScreen({required this.initialSearchText});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
|
||||
final searchTextController =
|
||||
useTextEditingController(text: initialSearchText);
|
||||
|
||||
useListenableCallback(
|
||||
searchTextController,
|
||||
() async {
|
||||
ref
|
||||
.read(searchSuggestionsProvider().notifier)
|
||||
.addQuery(searchTextController.text);
|
||||
|
||||
await ref.read(tabSearchRepositoryProvider.notifier).addQuery(
|
||||
searchTextController.text,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
matchPrefix: _matchPrefix,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
matchSuffix: _matchPrefix,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final globalSelectedContainer = ref.watch(
|
||||
selectedContainerDataProvider.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
final defaultSearchBang = ref.watch(
|
||||
defaultSearchBangDataProvider.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
final selectedBang = useState<BangData?>(null);
|
||||
final activeBang = selectedBang.value ?? defaultSearchBang;
|
||||
final showBangIcon = useState(false);
|
||||
|
||||
ref.listen(
|
||||
selectedBangDataProvider(),
|
||||
(previous, next) {
|
||||
if (next.hasValue) {
|
||||
if ((previous?.hasValue ?? false) && previous!.value != next.value) {
|
||||
showBangIcon.value = true;
|
||||
}
|
||||
|
||||
selectedBang.value = next.value;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> submitSearch(String query) async {
|
||||
if (activeBang != null && (formKey.currentState?.validate() == true)) {
|
||||
final searchUri = await ref.read(
|
||||
triggerBangSearchProvider(activeBang, query).future,
|
||||
);
|
||||
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final selectedContainer = useState<ContainerData?>(globalSelectedContainer);
|
||||
|
||||
final availableTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(selectedContainer.value?.id).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
return Scaffold(
|
||||
body: Form(
|
||||
key: formKey,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
automaticallyImplyLeading: false,
|
||||
title: SearchField(
|
||||
showBangIcon: showBangIcon.value,
|
||||
textEditingController: searchTextController,
|
||||
onFieldSubmitted: (text) {},
|
||||
activeBang: activeBang,
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: Divider(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Search Provider',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
BangChips(
|
||||
activeBang: activeBang,
|
||||
onSelected: (bang) {
|
||||
ref
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(bang.trigger);
|
||||
},
|
||||
onDeleted: (bang) async {
|
||||
if (ref.read(selectedBangTriggerProvider()) ==
|
||||
bang.trigger) {
|
||||
ref
|
||||
.read(
|
||||
selectedBangTriggerProvider().notifier,
|
||||
)
|
||||
.clearTrigger();
|
||||
} else {
|
||||
final dialogResult = await BangChips.resetBangDialog(
|
||||
context,
|
||||
bang.trigger,
|
||||
);
|
||||
|
||||
if (dialogResult == true) {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.resetFrequency(bang.trigger);
|
||||
}
|
||||
}
|
||||
},
|
||||
searchTextController: searchTextController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SearchSuggestionList(
|
||||
searchTextController: searchTextController,
|
||||
submitSearch: submitSearch,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: Divider(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Tabs',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
ContainerChips(
|
||||
displayMenu: false,
|
||||
selectedContainer: selectedContainer.value,
|
||||
onSelected: (container) {
|
||||
selectedContainer.value = container;
|
||||
},
|
||||
onDeleted: (container) {
|
||||
selectedContainer.value = null;
|
||||
},
|
||||
searchTextController: searchTextController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final tabs = ref.watch(tabSearchRepositoryProvider);
|
||||
|
||||
return SliverSkeletonizer(
|
||||
enabled: tabs.isLoading,
|
||||
child: tabs.when(
|
||||
data: (data) {
|
||||
if (data == null) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
|
||||
return SliverList.builder(
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
final result = data[index];
|
||||
|
||||
final headHasMatch =
|
||||
result.title?.contains(_matchPrefix) ?? false;
|
||||
final bodyResult =
|
||||
result.extractedContent ?? result.fullContent;
|
||||
|
||||
return ListTile(
|
||||
title: result.title.mapNotNull(
|
||||
(title) => MarkdownBody(data: title),
|
||||
),
|
||||
subtitle: (!headHasMatch && bodyResult != null)
|
||||
? MarkdownBody(data: bodyResult)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) {
|
||||
return FailureWidget(
|
||||
title: 'Could not load tabs',
|
||||
exception: error,
|
||||
);
|
||||
},
|
||||
loading: () => SliverList.builder(
|
||||
itemCount: tabs.valueOrNull?.length ?? 3,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Bone.text(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: availableTabs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Consumer(
|
||||
key: ValueKey(availableTabs[index]),
|
||||
builder: (context, ref, child) {
|
||||
final tab =
|
||||
ref.watch(tabStateProvider(availableTabs[index]));
|
||||
|
||||
if (tab == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: RepaintBoundary(
|
||||
child: RawImage(
|
||||
image: tab.icon?.value,
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
),
|
||||
title: Text(tab.title),
|
||||
subtitle: Text(
|
||||
tab.url.toString(),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +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/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/search/domain/providers/search_suggestions.dart';
|
||||
|
||||
class SearchSuggestionList extends HookConsumerWidget {
|
||||
final TextEditingController searchTextController;
|
||||
final Future<void> Function(String query) submitSearch;
|
||||
final bool showHistory;
|
||||
|
||||
const SearchSuggestionList({
|
||||
required this.searchTextController,
|
||||
required this.submitSearch,
|
||||
this.showHistory = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
useListenableSelector(
|
||||
searchTextController,
|
||||
() => searchTextController.text.isNotEmpty,
|
||||
);
|
||||
|
||||
final suggestions = useStream(ref.watch(searchSuggestionsProvider()));
|
||||
|
||||
final searchHistory = ref.watch(searchHistoryProvider);
|
||||
|
||||
final searchText = searchTextController.text;
|
||||
|
||||
if (showHistory &&
|
||||
(!searchText.isNotEmpty || !suggestions.hasData) &&
|
||||
(searchHistory.value?.isNotEmpty ?? false)) {
|
||||
final entries = searchHistory.value!;
|
||||
|
||||
return SliverList.builder(
|
||||
itemCount: entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final query = entries[index].searchQuery;
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: Text(query),
|
||||
onLongPress: () {
|
||||
searchTextController.text = query;
|
||||
},
|
||||
onTap: () async {
|
||||
await submitSearch(query);
|
||||
},
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.removeSearchEntry(query);
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final prioritizedSuggestions = [
|
||||
if (searchText.isNotEmpty) searchText,
|
||||
if (suggestions.data != null)
|
||||
...suggestions.data!.whereNot((suggestion) => suggestion == searchText),
|
||||
];
|
||||
|
||||
return SliverList.builder(
|
||||
itemCount: prioritizedSuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = prioritizedSuggestions[index];
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.search),
|
||||
title: Text(suggestion),
|
||||
onLongPress: () {
|
||||
searchTextController.text = suggestion;
|
||||
},
|
||||
onTap: () async {
|
||||
await submitSearch(suggestion);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,83 @@ class TokenizedFilter<T> {
|
||||
);
|
||||
}
|
||||
|
||||
factory TokenizedFilter({
|
||||
static double _calculateScore(
|
||||
List<String> queryTokens,
|
||||
List<String> targetTokens,
|
||||
) {
|
||||
if (queryTokens.isEmpty || targetTokens.isEmpty) return 0.0;
|
||||
|
||||
var score = 0.0;
|
||||
var exactMatches = 0;
|
||||
var partialMatches = 0;
|
||||
|
||||
for (final queryToken in queryTokens) {
|
||||
var hasMatch = false;
|
||||
|
||||
for (int i = 0; i < targetTokens.length; i++) {
|
||||
final targetToken = targetTokens[i];
|
||||
|
||||
if (targetToken == queryToken) {
|
||||
// Exact match gets 1.0 points
|
||||
score += 1.0;
|
||||
exactMatches++;
|
||||
hasMatch = true;
|
||||
break;
|
||||
} else if (targetToken.contains(queryToken)) {
|
||||
// Partial match gets points based on match length ratio
|
||||
final ratio = queryToken.length / targetToken.length;
|
||||
score += ratio * 0.8; // Partial matches worth 80% of exact matches
|
||||
partialMatches++;
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMatch) {
|
||||
score -= 0.2; // Penalty for unmatched query tokens
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize score based on number of query tokens and matches
|
||||
final matchRatio =
|
||||
(exactMatches + partialMatches * 0.8) / queryTokens.length;
|
||||
score = score / queryTokens.length; // Normalize by query length
|
||||
|
||||
// Apply match ratio as a multiplier
|
||||
score *= matchRatio;
|
||||
|
||||
// Ensure score is between 0 and 1
|
||||
return score.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
factory TokenizedFilter.sort({
|
||||
required Iterable<T> items,
|
||||
required ToString<T> toString,
|
||||
required String query,
|
||||
}) {
|
||||
final queryTokens = _tokenize(query);
|
||||
|
||||
final scoredItems = items.map((item) {
|
||||
final itemString = toString(item);
|
||||
if (itemString == null || itemString.isEmpty) {
|
||||
return (item, 0.0);
|
||||
}
|
||||
|
||||
final itemTokens = _tokenize(itemString);
|
||||
final score = _calculateScore(queryTokens, itemTokens);
|
||||
return (item, score);
|
||||
}).toList();
|
||||
|
||||
// Sort by score in descending order
|
||||
scoredItems.sort((a, b) => b.$2.compareTo(a.$2));
|
||||
|
||||
// Extract just the items in sorted order
|
||||
final sorted = scoredItems.map((e) => e.$1).toList();
|
||||
|
||||
return TokenizedFilter._(items, sorted);
|
||||
}
|
||||
|
||||
factory TokenizedFilter.remove({
|
||||
required Iterable<T> items,
|
||||
required ToString<T> toString,
|
||||
required String query,
|
||||
|
||||
@@ -2,11 +2,13 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
AsyncSnapshot<T> useCachedFuture<T>(
|
||||
Future<T> Function() valueBuilder,
|
||||
) {
|
||||
Future<T> Function() valueBuilder, [
|
||||
List<Object?> keys = const <Object>[],
|
||||
]) {
|
||||
// ignore: discarded_futures
|
||||
final cachedFuture = useMemoized(
|
||||
valueBuilder,
|
||||
keys,
|
||||
);
|
||||
return useFuture(cachedFuture);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
void useListenableCallback(Listenable? listenable, void Function() callback,
|
||||
[List<Object?>? keys]) {
|
||||
void useListenableCallback(
|
||||
Listenable? listenable,
|
||||
void Function() callback, [
|
||||
List<Object?>? keys,
|
||||
]) {
|
||||
useEffect(
|
||||
() {
|
||||
listenable?.addListener(callback);
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
|
||||
class AutoSuggestTextField extends HookWidget {
|
||||
final TextEditingController controller;
|
||||
final String? suggestion;
|
||||
final TextStyle? style;
|
||||
final TextStyle? labelStyle;
|
||||
final InputDecoration? decoration;
|
||||
final TextInputType? keyboardType;
|
||||
final TextInputAction? textInputAction;
|
||||
final TextCapitalization textCapitalization;
|
||||
final bool autofocus;
|
||||
final bool obscureText;
|
||||
final int? maxLines;
|
||||
final int? minLines;
|
||||
final int? maxLength;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final VoidCallback? onEditingComplete;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final bool? enabled;
|
||||
final FocusNode? focusNode;
|
||||
final Color? cursorColor;
|
||||
final Color? suggestionHighlightColor;
|
||||
final bool? enableIMEPersonalizedLearning;
|
||||
final TapRegionCallback? onTapOutside;
|
||||
|
||||
const AutoSuggestTextField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.suggestion,
|
||||
this.style,
|
||||
this.labelStyle,
|
||||
this.decoration,
|
||||
this.keyboardType,
|
||||
this.textInputAction,
|
||||
this.textCapitalization = TextCapitalization.none,
|
||||
this.autofocus = false,
|
||||
this.obscureText = false,
|
||||
this.maxLines = 1,
|
||||
this.minLines,
|
||||
this.maxLength,
|
||||
this.onChanged,
|
||||
this.onEditingComplete,
|
||||
this.onSubmitted,
|
||||
this.inputFormatters,
|
||||
this.enabled,
|
||||
this.focusNode,
|
||||
this.cursorColor,
|
||||
this.suggestionHighlightColor,
|
||||
this.enableIMEPersonalizedLearning = true,
|
||||
this.onTapOutside,
|
||||
});
|
||||
|
||||
bool _suggestionHasMatch() =>
|
||||
suggestion != null &&
|
||||
controller.text.isNotEmpty &&
|
||||
suggestion!.startsWith(controller.text);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseDecoration = decoration ?? const InputDecoration();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
if (suggestion != null)
|
||||
AbsorbPointer(
|
||||
child: TextField(
|
||||
decoration: baseDecoration.copyWith(
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
label: HookBuilder(
|
||||
builder: (context) {
|
||||
final text = useListenableSelector(
|
||||
controller,
|
||||
() => controller.text,
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
TextSelection? lastSelection;
|
||||
|
||||
void handleSelectionChange() {
|
||||
if (lastSelection != controller.selection) {
|
||||
lastSelection = controller.selection;
|
||||
if (lastSelection!.start != lastSelection!.end) {
|
||||
if (_suggestionHasMatch()) {
|
||||
controller.value = controller.value.copyWith(
|
||||
text: suggestion,
|
||||
selection: lastSelection!.expandTo(
|
||||
TextPosition(offset: suggestion!.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller.addListener(handleSelectionChange);
|
||||
return () =>
|
||||
controller.removeListener(handleSelectionChange);
|
||||
});
|
||||
|
||||
if (!_suggestionHasMatch()) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: (style ?? Theme.of(context).textTheme.bodyLarge)
|
||||
?.copyWith(color: Colors.transparent),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: suggestion!.substring(text.length),
|
||||
style: TextStyle(
|
||||
height: 1.2,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
backgroundColor: suggestionHighlightColor ??
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withValues(alpha: 0.40),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
decoration: baseDecoration.copyWith(
|
||||
label: baseDecoration.label ?? const Text(''),
|
||||
floatingLabelBehavior: baseDecoration.floatingLabelBehavior ??
|
||||
FloatingLabelBehavior.never,
|
||||
),
|
||||
style: style,
|
||||
keyboardType: keyboardType,
|
||||
textInputAction: textInputAction,
|
||||
textCapitalization: textCapitalization,
|
||||
autofocus: autofocus,
|
||||
obscureText: obscureText,
|
||||
maxLines: maxLines,
|
||||
minLines: minLines,
|
||||
maxLength: maxLength,
|
||||
onChanged: onChanged,
|
||||
onEditingComplete: onEditingComplete,
|
||||
onSubmitted: onSubmitted.mapNotNull(
|
||||
(onSubmitted) => (value) {
|
||||
if (_suggestionHasMatch()) {
|
||||
onSubmitted(suggestion!);
|
||||
} else {
|
||||
onSubmitted(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
inputFormatters: inputFormatters,
|
||||
enabled: enabled,
|
||||
cursorColor: cursorColor,
|
||||
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning ?? true,
|
||||
onTapOutside: onTapOutside,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/domain/services/generic_website.dart';
|
||||
import 'package:lensai/presentation/hooks/cached_future.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class UrlIcon extends HookConsumerWidget {
|
||||
final double iconSize;
|
||||
final Uri url;
|
||||
|
||||
const UrlIcon(this.url, {required this.iconSize, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final icon = useCachedFuture(
|
||||
() async =>
|
||||
ref.read(genericWebsiteServiceProvider.notifier).getUrlIcon(url),
|
||||
[url],
|
||||
);
|
||||
|
||||
return Skeletonizer(
|
||||
enabled: !icon.hasData,
|
||||
child: SizedBox.square(
|
||||
dimension: iconSize,
|
||||
child: (icon.data != null)
|
||||
? RepaintBoundary(
|
||||
child: RawImage(
|
||||
image: icon.data?.image.value,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
MdiIcons.web,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ dependencies:
|
||||
rxdart: ^0.28.0
|
||||
share_plus: ^10.1.4
|
||||
skeletonizer: ^1.4.3
|
||||
sliver_tools: ^0.2.12
|
||||
speech_to_text_google_dialog:
|
||||
git:
|
||||
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
||||
|
||||
+26
-1
@@ -1,7 +1,9 @@
|
||||
package eu.lensai.flutter_mozilla_components.api
|
||||
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl.Companion
|
||||
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.AutocompleteResult
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestion
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionApi
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionEvents
|
||||
@@ -10,6 +12,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||
|
||||
class GeckoSuggestionApiImpl(
|
||||
@@ -23,7 +27,28 @@ class GeckoSuggestionApiImpl(
|
||||
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||
}
|
||||
|
||||
override fun onInputChanged(text: String, providers: List<GeckoSuggestionType>) {
|
||||
override fun getAutocompleteSuggestion(
|
||||
query: String,
|
||||
callback: (Result<AutocompleteResult?>) -> Unit
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
val suggestion = components.core.historyStorage.getAutocompleteSuggestion(query)
|
||||
|
||||
callback(Result.success( suggestion?.let { AutocompleteResult(
|
||||
input = it.input,
|
||||
url = it.url,
|
||||
text = it.text,
|
||||
totalItems = it.totalItems.toLong(),
|
||||
source = it.source
|
||||
)}
|
||||
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun querySuggestions(text: String, providers: List<GeckoSuggestionType>) {
|
||||
for(provider in providers) {
|
||||
coroutineScope.launch {
|
||||
val results = when(provider) {
|
||||
|
||||
+63
-3
@@ -1157,6 +1157,36 @@ data class GeckoEngineSettings (
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class AutocompleteResult (
|
||||
val input: String,
|
||||
val text: String,
|
||||
val url: String,
|
||||
val source: String,
|
||||
val totalItems: Long
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
fun fromList(pigeonVar_list: List<Any?>): AutocompleteResult {
|
||||
val input = pigeonVar_list[0] as String
|
||||
val text = pigeonVar_list[1] as String
|
||||
val url = pigeonVar_list[2] as String
|
||||
val source = pigeonVar_list[3] as String
|
||||
val totalItems = pigeonVar_list[4] as Long
|
||||
return AutocompleteResult(input, text, url, source, totalItems)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
input,
|
||||
text,
|
||||
url,
|
||||
source,
|
||||
totalItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return when (type) {
|
||||
@@ -1365,6 +1395,11 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
GeckoEngineSettings.fromList(it)
|
||||
}
|
||||
}
|
||||
170.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
AutocompleteResult.fromList(it)
|
||||
}
|
||||
}
|
||||
else -> super.readValueOfType(type, buffer)
|
||||
}
|
||||
}
|
||||
@@ -1534,6 +1569,10 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
stream.write(169)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is AutocompleteResult -> {
|
||||
stream.write(170)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
@@ -3129,7 +3168,8 @@ class GeckoAddonEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface GeckoSuggestionApi {
|
||||
fun onInputChanged(text: String, providers: List<GeckoSuggestionType>)
|
||||
fun getAutocompleteSuggestion(query: String, callback: (Result<AutocompleteResult?>) -> Unit)
|
||||
fun querySuggestions(text: String, providers: List<GeckoSuggestionType>)
|
||||
|
||||
companion object {
|
||||
/** The codec used by GeckoSuggestionApi. */
|
||||
@@ -3141,14 +3181,34 @@ interface GeckoSuggestionApi {
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: GeckoSuggestionApi?, messageChannelSuffix: String = "") {
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.onInputChanged$separatedMessageChannelSuffix", codec)
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.getAutocompleteSuggestion$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val queryArg = args[0] as String
|
||||
api.getAutocompleteSuggestion(queryArg) { result: Result<AutocompleteResult?> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(wrapResult(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.querySuggestions$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val textArg = args[0] as String
|
||||
val providersArg = args[1] as List<GeckoSuggestionType>
|
||||
val wrapped: List<Any?> = try {
|
||||
api.onInputChanged(textArg, providersArg)
|
||||
api.querySuggestions(textArg, providersArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapError(exception)
|
||||
|
||||
+116
-115
@@ -3,154 +3,155 @@
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
android:layout_marginBottom="20dp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
android:id="@+id/author_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:text="@string/mozac_feature_addons_author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:text="@string/mozac_feature_addons_author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
android:id="@+id/author_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<View
|
||||
android:id="@+id/author_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/author_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
android:id="@+id/author_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/author_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/author_divider"
|
||||
android:text="@string/mozac_feature_addons_version" />
|
||||
android:id="@+id/version_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/author_divider"
|
||||
android:text="@string/mozac_feature_addons_version" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/author_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="1.2.3" />
|
||||
android:id="@+id/version_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/author_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="1.2.3" />
|
||||
|
||||
<View
|
||||
android:id="@+id/version_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/version_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
android:id="@+id/version_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/version_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_updated_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/version_divider"
|
||||
android:text="@string/mozac_feature_addons_last_updated" />
|
||||
android:id="@+id/last_updated_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/version_divider"
|
||||
android:text="@string/mozac_feature_addons_last_updated" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_updated_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/version_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="Oct 16, 2019" />
|
||||
android:id="@+id/last_updated_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/version_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="Oct 16, 2019" />
|
||||
|
||||
<View
|
||||
android:id="@+id/last_updated_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/last_updated_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
android:id="@+id/last_updated_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/last_updated_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_page_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/last_updated_divider"
|
||||
android:text="@string/mozac_feature_addons_home_page" />
|
||||
android:id="@+id/home_page_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/last_updated_divider"
|
||||
android:text="@string/mozac_feature_addons_home_page" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/home_page_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/last_updated_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@string/mozac_feature_addons_home_page"
|
||||
android:src="@drawable/mozac_ic_link_24"
|
||||
app:tint="@android:color/black" />
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/home_page_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/last_updated_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@string/mozac_feature_addons_home_page"
|
||||
app:srcCompat="@drawable/mozac_ic_link_24"
|
||||
app:tint="@android:color/black" />
|
||||
|
||||
<View
|
||||
android:id="@+id/home_page_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/home_page_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
android:id="@+id/home_page_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/home_page_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rating_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:text="@string/mozac_feature_addons_rating" />
|
||||
android:id="@+id/rating_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:text="@string/mozac_feature_addons_rating" />
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/rating_view"
|
||||
style="@style/Widget.AppCompat.RatingBar.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:layout_toStartOf="@+id/users_count"
|
||||
android:isIndicator="true"
|
||||
android:numStars="5" />
|
||||
android:id="@+id/rating_view"
|
||||
style="@style/Widget.AppCompat.RatingBar.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:layout_toStartOf="@+id/users_count"
|
||||
android:isIndicator="true"
|
||||
android:numStars="5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/users_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
tools:text="591,642" />
|
||||
android:id="@+id/users_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
tools:text="591,642" />
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
|
||||
+1
-1
@@ -7,5 +7,5 @@
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
|
||||
|
||||
+21
-20
@@ -4,29 +4,30 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".addons.PermissionsDetailsActivity">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:context=".addons.PermissionsDetailsActivity">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/add_ons_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:id="@+id/add_ons_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/learn_more_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/add_ons_permissions"
|
||||
app:drawableEndCompat="@drawable/mozac_ic_link_24"
|
||||
android:padding="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:text="@string/mozac_feature_addons_learn_more"
|
||||
app:drawableTint="@android:color/black"
|
||||
android:textColor="@android:color/black" />
|
||||
android:id="@+id/learn_more_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/add_ons_permissions"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/mozac_feature_addons_learn_more"
|
||||
android:textColor="@android:color/black"
|
||||
app:drawableEndCompat="@drawable/mozac_ic_link_24"
|
||||
app:drawableTint="@android:color/black" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
+1
@@ -8,4 +8,5 @@
|
||||
android:id="@+id/addonSettingsContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
|
||||
+11
-10
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
@@ -77,7 +77,7 @@
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_below="@+id/permissions"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:checked="false"
|
||||
android:checked="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/mozac_feature_addons_settings_allow_in_private_browsing"
|
||||
@@ -85,12 +85,13 @@
|
||||
android:textSize="18sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/remove_add_on"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/allow_in_private_browsing_switch"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textColor="@color/photonRed50"
|
||||
android:text="@string/mozac_feature_addons_remove" />
|
||||
android:id="@+id/remove_add_on"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/allow_in_private_browsing_switch"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/mozac_feature_addons_remove"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/photonRed50" />
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
|
||||
+18
-21
@@ -4,28 +4,25 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/add_on_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/add_on_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permission"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:textColor="@android:color/black"
|
||||
tools:text="Access your data for all websites" />
|
||||
android:id="@+id/permission"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="Access your data for all websites" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider"
|
||||
android:importantForAccessibility="no" />
|
||||
</LinearLayout>
|
||||
|
||||
+35
-15
@@ -2,23 +2,43 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/addonProgressOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="1dp"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
android:elevation="1dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:drawableStartCompat="@drawable/mozac_ic_extensions_black"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/mozac_extension_install_progress_caption"/>
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/install_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:padding="16dp"
|
||||
android:text="@string/mozac_extension_install_progress_caption"
|
||||
app:drawableStartCompat="@drawable/mozac_ic_extensions_black" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/install_hint"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/mozac_feature_addons_install_addon_dialog_cancel"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ final _apiInstance = GeckoSuggestionApi();
|
||||
|
||||
class GeckoSuggestionsService extends GeckoSuggestionEvents {
|
||||
final GeckoSuggestionApi _api;
|
||||
final _suggestionsSubject = BehaviorSubject<List<GeckoSuggestion>>();
|
||||
final _suggestionsSubject = PublishSubject<List<GeckoSuggestion>>();
|
||||
|
||||
Stream<List<GeckoSuggestion>> get suggestionsStream =>
|
||||
_suggestionsSubject.stream;
|
||||
@@ -26,7 +26,7 @@ class GeckoSuggestionsService extends GeckoSuggestionEvents {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onInputChanged(
|
||||
Future<void> querySuggestions(
|
||||
String text, {
|
||||
List<GeckoSuggestionType> providers = const [
|
||||
GeckoSuggestionType.session,
|
||||
@@ -34,7 +34,11 @@ class GeckoSuggestionsService extends GeckoSuggestionEvents {
|
||||
GeckoSuggestionType.history,
|
||||
],
|
||||
}) {
|
||||
return _api.onInputChanged(text, providers);
|
||||
return _api.querySuggestions(text, providers);
|
||||
}
|
||||
|
||||
Future<AutocompleteResult?> getAutocompleteSuggestion(String query) {
|
||||
return _api.getAutocompleteSuggestion(query);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1273,6 +1273,47 @@ class GeckoEngineSettings {
|
||||
}
|
||||
}
|
||||
|
||||
class AutocompleteResult {
|
||||
AutocompleteResult({
|
||||
required this.input,
|
||||
required this.text,
|
||||
required this.url,
|
||||
required this.source,
|
||||
required this.totalItems,
|
||||
});
|
||||
|
||||
String input;
|
||||
|
||||
String text;
|
||||
|
||||
String url;
|
||||
|
||||
String source;
|
||||
|
||||
int totalItems;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
input,
|
||||
text,
|
||||
url,
|
||||
source,
|
||||
totalItems,
|
||||
];
|
||||
}
|
||||
|
||||
static AutocompleteResult decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return AutocompleteResult(
|
||||
input: result[0]! as String,
|
||||
text: result[1]! as String,
|
||||
url: result[2]! as String,
|
||||
source: result[3]! as String,
|
||||
totalItems: result[4]! as int,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@@ -1404,6 +1445,9 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
} else if (value is GeckoEngineSettings) {
|
||||
buffer.putUint8(169);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is AutocompleteResult) {
|
||||
buffer.putUint8(170);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
@@ -1507,6 +1551,8 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
return TabContent.decode(readValue(buffer)!);
|
||||
case 169:
|
||||
return GeckoEngineSettings.decode(readValue(buffer)!);
|
||||
case 170:
|
||||
return AutocompleteResult.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
@@ -3501,8 +3547,30 @@ class GeckoSuggestionApi {
|
||||
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
Future<void> onInputChanged(String text, List<GeckoSuggestionType> providers) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.onInputChanged$pigeonVar_messageChannelSuffix';
|
||||
Future<AutocompleteResult?> getAutocompleteSuggestion(String query) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.getAutocompleteSuggestion$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_channel.send(<Object?>[query]) as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as AutocompleteResult?);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> querySuggestions(String text, List<GeckoSuggestionType> providers) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.querySuggestions$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
|
||||
@@ -596,6 +596,17 @@ class GeckoEngineSettings {
|
||||
);
|
||||
}
|
||||
|
||||
class AutocompleteResult {
|
||||
final String input;
|
||||
final String text;
|
||||
final String url;
|
||||
final String source;
|
||||
final int totalItems;
|
||||
|
||||
AutocompleteResult(
|
||||
this.input, this.text, this.url, this.source, this.totalItems);
|
||||
}
|
||||
|
||||
// /// Represents all the different supported types of data that can be found from long clicking
|
||||
// /// an element.
|
||||
// sealed class HitResult {
|
||||
@@ -998,7 +1009,9 @@ abstract class GeckoAddonEvents {
|
||||
|
||||
@HostApi()
|
||||
abstract class GeckoSuggestionApi {
|
||||
void onInputChanged(String text, List<GeckoSuggestionType> providers);
|
||||
@async
|
||||
AutocompleteResult? getAutocompleteSuggestion(String query);
|
||||
void querySuggestions(String text, List<GeckoSuggestionType> providers);
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
|
||||
Reference in New Issue
Block a user