added many suggestion modules

This commit is contained in:
Fabian Freund
2026-03-09 03:10:02 +01:00
parent 84038c7b81
commit a7b4954da1
35 changed files with 4593 additions and 2077 deletions
@@ -66,8 +66,7 @@ class TranslationState with FastEquatable {
displayError: data.displayError, displayError: data.displayError,
); );
bool get hasError => bool get hasError => translationErrorName != null && (displayError ?? true);
translationErrorName != null && (displayError ?? true);
@override @override
List<Object?> get hashParameters => [ List<Object?> get hashParameters => [
@@ -136,6 +136,7 @@ class BookmarkSearchResults extends _$BookmarkSearchResults {
} }
final results = await _service.searchBookmarks(query, limit: limit); final results = await _service.searchBookmarks(query, limit: limit);
if (!ref.mounted) return;
state = results state = results
.map(BookmarkItem.parseRecursive) .map(BookmarkItem.parseRecursive)
.whereType<BookmarkEntry>() .whereType<BookmarkEntry>()
@@ -86,7 +86,7 @@ final class BookmarkSearchResultsProvider
} }
String _$bookmarkSearchResultsHash() => String _$bookmarkSearchResultsHash() =>
r'f1413466db26c1e3407501453b8cec97504a771d'; r'44af94b2234c817b08b0bde2dc68f9ab8d2d0a9a';
abstract class _$BookmarkSearchResults extends $Notifier<List<BookmarkEntry>> { abstract class _$BookmarkSearchResults extends $Notifier<List<BookmarkEntry>> {
List<BookmarkEntry> build(); List<BookmarkEntry> build();
@@ -47,7 +47,7 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
part 'providers.g.dart'; part 'providers.g.dart';
typedef TabStateWirthContainer = (TabState, ContainerData?); typedef TabStateWithContainer = (TabState, ContainerData?);
@Riverpod() @Riverpod()
bool canManualTabReorder(Ref ref) { bool canManualTabReorder(Ref ref) {
@@ -175,7 +175,7 @@ EquatableValue<Map<String, TabState>> containerTabStates(
} }
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) { EquatableValue<List<TabStateWithContainer>> fifoTabStates(Ref ref) {
final containerData = ref final containerData = ref
.watch(watchContainersWithCountProvider.select((value) => value.value)) .watch(watchContainersWithCountProvider.select((value) => value.value))
.mapNotNull( .mapNotNull(
@@ -202,7 +202,7 @@ EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
} }
@Riverpod() @Riverpod()
EquatableValue<List<TabStateWirthContainer>> EquatableValue<List<TabStateWithContainer>>
selectedContainerTabStatesWithContainer(Ref ref) { selectedContainerTabStatesWithContainer(Ref ref) {
final filter = ref.watch( final filter = ref.watch(
selectedContainerProvider.select( selectedContainerProvider.select(
@@ -256,7 +256,7 @@ selectedContainerTabStatesWithContainer(Ref ref) {
} }
@Riverpod() @Riverpod()
EquatableValue<List<TabStateWirthContainer>> quickTabSwitcherTabStates( EquatableValue<List<TabStateWithContainer>> quickTabSwitcherTabStates(
Ref ref, Ref ref,
QuickTabSwitcherMode mode, QuickTabSwitcherMode mode,
) { ) {
@@ -430,11 +430,11 @@ final fifoTabStatesProvider = FifoTabStatesProvider._();
final class FifoTabStatesProvider final class FifoTabStatesProvider
extends extends
$FunctionalProvider< $FunctionalProvider<
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
EquatableValue<List<TabStateWirthContainer>> EquatableValue<List<TabStateWithContainer>>
> >
with $Provider<EquatableValue<List<TabStateWirthContainer>>> { with $Provider<EquatableValue<List<TabStateWithContainer>>> {
FifoTabStatesProvider._() FifoTabStatesProvider._()
: super( : super(
from: null, from: null,
@@ -451,30 +451,30 @@ final class FifoTabStatesProvider
@$internal @$internal
@override @override
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement( $ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
$ProviderPointer pointer, $ProviderPointer pointer,
) => $ProviderElement(pointer); ) => $ProviderElement(pointer);
@override @override
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) { EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
return fifoTabStates(ref); return fifoTabStates(ref);
} }
/// {@macro riverpod.override_with_value} /// {@macro riverpod.override_with_value}
Override overrideWithValue( Override overrideWithValue(
EquatableValue<List<TabStateWirthContainer>> value, EquatableValue<List<TabStateWithContainer>> value,
) { ) {
return $ProviderOverride( return $ProviderOverride(
origin: this, origin: this,
providerOverride: providerOverride:
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>( $SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
value, value,
), ),
); );
} }
} }
String _$fifoTabStatesHash() => r'03298f30559d55e986d97a17d517b9a9abb89be4'; String _$fifoTabStatesHash() => r'f8dc69382d307bc83193a3b839a16313e9c1ab69';
@ProviderFor(selectedContainerTabStatesWithContainer) @ProviderFor(selectedContainerTabStatesWithContainer)
final selectedContainerTabStatesWithContainerProvider = final selectedContainerTabStatesWithContainerProvider =
@@ -483,11 +483,11 @@ final selectedContainerTabStatesWithContainerProvider =
final class SelectedContainerTabStatesWithContainerProvider final class SelectedContainerTabStatesWithContainerProvider
extends extends
$FunctionalProvider< $FunctionalProvider<
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
EquatableValue<List<TabStateWirthContainer>> EquatableValue<List<TabStateWithContainer>>
> >
with $Provider<EquatableValue<List<TabStateWirthContainer>>> { with $Provider<EquatableValue<List<TabStateWithContainer>>> {
SelectedContainerTabStatesWithContainerProvider._() SelectedContainerTabStatesWithContainerProvider._()
: super( : super(
from: null, from: null,
@@ -505,23 +505,23 @@ final class SelectedContainerTabStatesWithContainerProvider
@$internal @$internal
@override @override
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement( $ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
$ProviderPointer pointer, $ProviderPointer pointer,
) => $ProviderElement(pointer); ) => $ProviderElement(pointer);
@override @override
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) { EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
return selectedContainerTabStatesWithContainer(ref); return selectedContainerTabStatesWithContainer(ref);
} }
/// {@macro riverpod.override_with_value} /// {@macro riverpod.override_with_value}
Override overrideWithValue( Override overrideWithValue(
EquatableValue<List<TabStateWirthContainer>> value, EquatableValue<List<TabStateWithContainer>> value,
) { ) {
return $ProviderOverride( return $ProviderOverride(
origin: this, origin: this,
providerOverride: providerOverride:
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>( $SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
value, value,
), ),
); );
@@ -529,7 +529,7 @@ final class SelectedContainerTabStatesWithContainerProvider
} }
String _$selectedContainerTabStatesWithContainerHash() => String _$selectedContainerTabStatesWithContainerHash() =>
r'e2dc85fc72ee29736d8fdaabb1abadd23cc4bac0'; r'187e408857ddc5fe08f8e5344beb510c3c39abe1';
@ProviderFor(quickTabSwitcherTabStates) @ProviderFor(quickTabSwitcherTabStates)
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._(); final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
@@ -537,11 +537,11 @@ final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
final class QuickTabSwitcherTabStatesProvider final class QuickTabSwitcherTabStatesProvider
extends extends
$FunctionalProvider< $FunctionalProvider<
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
EquatableValue<List<TabStateWirthContainer>> EquatableValue<List<TabStateWithContainer>>
> >
with $Provider<EquatableValue<List<TabStateWirthContainer>>> { with $Provider<EquatableValue<List<TabStateWithContainer>>> {
QuickTabSwitcherTabStatesProvider._({ QuickTabSwitcherTabStatesProvider._({
required QuickTabSwitcherTabStatesFamily super.from, required QuickTabSwitcherTabStatesFamily super.from,
required QuickTabSwitcherMode super.argument, required QuickTabSwitcherMode super.argument,
@@ -565,24 +565,24 @@ final class QuickTabSwitcherTabStatesProvider
@$internal @$internal
@override @override
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement( $ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
$ProviderPointer pointer, $ProviderPointer pointer,
) => $ProviderElement(pointer); ) => $ProviderElement(pointer);
@override @override
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) { EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
final argument = this.argument as QuickTabSwitcherMode; final argument = this.argument as QuickTabSwitcherMode;
return quickTabSwitcherTabStates(ref, argument); return quickTabSwitcherTabStates(ref, argument);
} }
/// {@macro riverpod.override_with_value} /// {@macro riverpod.override_with_value}
Override overrideWithValue( Override overrideWithValue(
EquatableValue<List<TabStateWirthContainer>> value, EquatableValue<List<TabStateWithContainer>> value,
) { ) {
return $ProviderOverride( return $ProviderOverride(
origin: this, origin: this,
providerOverride: providerOverride:
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>( $SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
value, value,
), ),
); );
@@ -601,12 +601,12 @@ final class QuickTabSwitcherTabStatesProvider
} }
String _$quickTabSwitcherTabStatesHash() => String _$quickTabSwitcherTabStatesHash() =>
r'df990fb96cca62a272572c925028725f81a0e96b'; r'2627bcd25718472f0c8057e836d76bdaa7317c1c';
final class QuickTabSwitcherTabStatesFamily extends $Family final class QuickTabSwitcherTabStatesFamily extends $Family
with with
$FunctionalFamilyOverride< $FunctionalFamilyOverride<
EquatableValue<List<TabStateWirthContainer>>, EquatableValue<List<TabStateWithContainer>>,
QuickTabSwitcherMode QuickTabSwitcherMode
> { > {
QuickTabSwitcherTabStatesFamily._() QuickTabSwitcherTabStatesFamily._()
@@ -587,8 +587,9 @@ class _TranslatePageTile extends ConsumerWidget {
tabStateProvider(selectedTabId).select((s) => s?.translationState), tabStateProvider(selectedTabId).select((s) => s?.translationState),
); );
final readerActive = ref.watch( final readerActive = ref.watch(
tabStateProvider(selectedTabId) tabStateProvider(
.select((s) => s?.readerableState.active ?? false), selectedTabId,
).select((s) => s?.readerableState.active ?? false),
); );
// Hide when reader mode is active (Fenix-aligned) // Hide when reader mode is active (Fenix-aligned)
@@ -588,12 +588,14 @@ class TabMenu extends HookConsumerWidget {
builder: (context, ref, child) { builder: (context, ref, child) {
final engineState = ref.watch(translationEngineStateProvider); final engineState = ref.watch(translationEngineStateProvider);
final translationState = ref.watch( final translationState = ref.watch(
tabStateProvider(selectedTabId) tabStateProvider(
.select((s) => s?.translationState), selectedTabId,
).select((s) => s?.translationState),
); );
final readerActive = ref.watch( final readerActive = ref.watch(
tabStateProvider(selectedTabId) tabStateProvider(
.select((s) => s?.readerableState.active ?? false), selectedTabId,
).select((s) => s?.readerableState.active ?? false),
); );
// Hide when reader mode is active (Fenix-aligned) // Hide when reader mode is active (Fenix-aligned)
@@ -620,9 +622,7 @@ class TabMenu extends HookConsumerWidget {
); );
} }
}, },
child: Text( child: Text(isTranslated ? 'Translated' : 'Translate Page'),
isTranslated ? 'Translated' : 'Translate Page',
),
); );
}, },
), ),
@@ -132,8 +132,10 @@ extension HitResultX on HitResult {
HitResult withCleanedLink(String cleanedUrl) { HitResult withCleanedLink(String cleanedUrl) {
return switch (this) { return switch (this) {
UnknownHitResult() => UnknownHitResult(src: cleanedUrl), UnknownHitResult() => UnknownHitResult(src: cleanedUrl),
ImageSrcHitResult(:final src) => ImageSrcHitResult(:final src) => ImageSrcHitResult(
ImageSrcHitResult(src: src, uri: cleanedUrl), src: src,
uri: cleanedUrl,
),
_ => this, _ => this,
}; };
} }
@@ -56,6 +56,20 @@ class HistoryRepository extends _$HistoryRepository {
return _service.deleteVisit(info); return _service.deleteVisit(info);
} }
Future<List<HistoryHighlight>> getHistoryHighlights({
double viewTimeWeight = 10.0,
double frequencyWeight = 4.0,
required int limit,
}) {
return _service.getHistoryHighlights(
weights: HistoryHighlightWeights(
viewTime: viewTimeWeight,
frequency: frequencyWeight,
),
limit: limit,
);
}
@override @override
void build() {} void build() {}
} }
@@ -41,7 +41,7 @@ final class HistoryRepositoryProvider
} }
} }
String _$historyRepositoryHash() => r'f9ccc32275bb2f95edbe53b2245b4843e8d0c439'; String _$historyRepositoryHash() => r'19f883fdb644e34bc89483b4a8ddb60f54c52114';
abstract class _$HistoryRepository extends $Notifier<void> { abstract class _$HistoryRepository extends $Notifier<void> {
void build(); void build();
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
import 'package:weblibre/features/web_feed/domain/providers.dart';
part 'empty_state_content.g.dart';
@Riverpod()
Future<List<HistoryHighlight>> searchEmptyHistoryHighlights(
Ref ref, {
int count = 25,
}) async {
final highlights = await ref
.read(historyRepositoryProvider.notifier)
.getHistoryHighlights(limit: count);
if (!ref.mounted) return [];
return highlights.where((h) => Uri.tryParse(h.url) != null).toList();
}
@Riverpod()
Future<List<VisitInfo>> searchEmptyRecentHistory(
Ref ref, {
int count = 25,
}) async {
final visits = await ref
.read(historyRepositoryProvider.notifier)
.getVisitsPaginated(count: count);
if (!ref.mounted) return [];
return visits.where((v) => Uri.tryParse(v.url) != null).toList();
}
@Riverpod()
AsyncValue<List<FeedArticle>> searchEmptyRecentFeedArticles(
Ref ref, {
int count = 25,
}) {
final feeds = ref.watch(feedListProvider);
final hasFeeds = feeds.whenOrNull(data: (list) => list.isNotEmpty) ?? false;
if (!hasFeeds) {
return const AsyncValue.data([]);
}
return ref
.watch(feedArticleListProvider(null))
.whenData((articles) => articles.take(count).toList());
}
@Riverpod()
List<TabStateWithContainer> searchEmptyRecentTabs(Ref ref, {int count = 25}) {
final tabs = ref.watch(fifoTabStatesProvider).value;
return tabs.take(count).toList();
}
@@ -0,0 +1,339 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'empty_state_content.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(searchEmptyHistoryHighlights)
final searchEmptyHistoryHighlightsProvider =
SearchEmptyHistoryHighlightsFamily._();
final class SearchEmptyHistoryHighlightsProvider
extends
$FunctionalProvider<
AsyncValue<List<HistoryHighlight>>,
List<HistoryHighlight>,
FutureOr<List<HistoryHighlight>>
>
with
$FutureModifier<List<HistoryHighlight>>,
$FutureProvider<List<HistoryHighlight>> {
SearchEmptyHistoryHighlightsProvider._({
required SearchEmptyHistoryHighlightsFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'searchEmptyHistoryHighlightsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchEmptyHistoryHighlightsHash();
@override
String toString() {
return r'searchEmptyHistoryHighlightsProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<List<HistoryHighlight>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<HistoryHighlight>> create(Ref ref) {
final argument = this.argument as int;
return searchEmptyHistoryHighlights(ref, count: argument);
}
@override
bool operator ==(Object other) {
return other is SearchEmptyHistoryHighlightsProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$searchEmptyHistoryHighlightsHash() =>
r'f12206cb05c5c03ecdc2b84e3ffd20706aa4c986';
final class SearchEmptyHistoryHighlightsFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<HistoryHighlight>>, int> {
SearchEmptyHistoryHighlightsFamily._()
: super(
retry: null,
name: r'searchEmptyHistoryHighlightsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SearchEmptyHistoryHighlightsProvider call({int count = 25}) =>
SearchEmptyHistoryHighlightsProvider._(argument: count, from: this);
@override
String toString() => r'searchEmptyHistoryHighlightsProvider';
}
@ProviderFor(searchEmptyRecentHistory)
final searchEmptyRecentHistoryProvider = SearchEmptyRecentHistoryFamily._();
final class SearchEmptyRecentHistoryProvider
extends
$FunctionalProvider<
AsyncValue<List<VisitInfo>>,
List<VisitInfo>,
FutureOr<List<VisitInfo>>
>
with $FutureModifier<List<VisitInfo>>, $FutureProvider<List<VisitInfo>> {
SearchEmptyRecentHistoryProvider._({
required SearchEmptyRecentHistoryFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'searchEmptyRecentHistoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchEmptyRecentHistoryHash();
@override
String toString() {
return r'searchEmptyRecentHistoryProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<List<VisitInfo>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<VisitInfo>> create(Ref ref) {
final argument = this.argument as int;
return searchEmptyRecentHistory(ref, count: argument);
}
@override
bool operator ==(Object other) {
return other is SearchEmptyRecentHistoryProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$searchEmptyRecentHistoryHash() =>
r'bd7b9a67edfb1751d6d5cf6a8392516911796cb0';
final class SearchEmptyRecentHistoryFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<VisitInfo>>, int> {
SearchEmptyRecentHistoryFamily._()
: super(
retry: null,
name: r'searchEmptyRecentHistoryProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SearchEmptyRecentHistoryProvider call({int count = 25}) =>
SearchEmptyRecentHistoryProvider._(argument: count, from: this);
@override
String toString() => r'searchEmptyRecentHistoryProvider';
}
@ProviderFor(searchEmptyRecentFeedArticles)
final searchEmptyRecentFeedArticlesProvider =
SearchEmptyRecentFeedArticlesFamily._();
final class SearchEmptyRecentFeedArticlesProvider
extends
$FunctionalProvider<
AsyncValue<List<FeedArticle>>,
AsyncValue<List<FeedArticle>>,
AsyncValue<List<FeedArticle>>
>
with $Provider<AsyncValue<List<FeedArticle>>> {
SearchEmptyRecentFeedArticlesProvider._({
required SearchEmptyRecentFeedArticlesFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'searchEmptyRecentFeedArticlesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchEmptyRecentFeedArticlesHash();
@override
String toString() {
return r'searchEmptyRecentFeedArticlesProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<AsyncValue<List<FeedArticle>>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
AsyncValue<List<FeedArticle>> create(Ref ref) {
final argument = this.argument as int;
return searchEmptyRecentFeedArticles(ref, count: argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AsyncValue<List<FeedArticle>> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AsyncValue<List<FeedArticle>>>(
value,
),
);
}
@override
bool operator ==(Object other) {
return other is SearchEmptyRecentFeedArticlesProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$searchEmptyRecentFeedArticlesHash() =>
r'ee6ac7336d58b78c6c9295cdbb8aeeca9a91c2ad';
final class SearchEmptyRecentFeedArticlesFamily extends $Family
with $FunctionalFamilyOverride<AsyncValue<List<FeedArticle>>, int> {
SearchEmptyRecentFeedArticlesFamily._()
: super(
retry: null,
name: r'searchEmptyRecentFeedArticlesProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SearchEmptyRecentFeedArticlesProvider call({int count = 25}) =>
SearchEmptyRecentFeedArticlesProvider._(argument: count, from: this);
@override
String toString() => r'searchEmptyRecentFeedArticlesProvider';
}
@ProviderFor(searchEmptyRecentTabs)
final searchEmptyRecentTabsProvider = SearchEmptyRecentTabsFamily._();
final class SearchEmptyRecentTabsProvider
extends
$FunctionalProvider<
List<TabStateWithContainer>,
List<TabStateWithContainer>,
List<TabStateWithContainer>
>
with $Provider<List<TabStateWithContainer>> {
SearchEmptyRecentTabsProvider._({
required SearchEmptyRecentTabsFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'searchEmptyRecentTabsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchEmptyRecentTabsHash();
@override
String toString() {
return r'searchEmptyRecentTabsProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<List<TabStateWithContainer>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
List<TabStateWithContainer> create(Ref ref) {
final argument = this.argument as int;
return searchEmptyRecentTabs(ref, count: argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<TabStateWithContainer> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<TabStateWithContainer>>(value),
);
}
@override
bool operator ==(Object other) {
return other is SearchEmptyRecentTabsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$searchEmptyRecentTabsHash() =>
r'6000970dd261f243326900555967956508b84660';
final class SearchEmptyRecentTabsFamily extends $Family
with $FunctionalFamilyOverride<List<TabStateWithContainer>, int> {
SearchEmptyRecentTabsFamily._()
: super(
retry: null,
name: r'searchEmptyRecentTabsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SearchEmptyRecentTabsProvider call({int count = 25}) =>
SearchEmptyRecentTabsProvider._(argument: count, from: this);
@override
String toString() => r'searchEmptyRecentTabsProvider';
}
@@ -21,7 +21,17 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'search_modules_view.g.dart'; part 'search_modules_view.g.dart';
enum SearchModuleType { tabs, articles, bookmarks, history } enum SearchModuleType {
tabs,
articles,
bookmarks,
history,
historyHighlights,
recentHistory,
recentArticles,
recentTabs,
containers,
}
enum SearchModuleDisplayState { preview, expanded, collapsed } enum SearchModuleDisplayState { preview, expanded, collapsed }
@@ -35,6 +35,11 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/containers_section.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/history_highlights_section.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_feed_articles_section.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_history_section.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_tabs_section.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart';
@@ -45,6 +50,8 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/isolatio
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart';
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart'; import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
@@ -135,6 +142,17 @@ class SearchScreen extends HookConsumerWidget {
source: searchTextController, source: searchTextController,
sampleDuration: const Duration(milliseconds: 150), sampleDuration: const Duration(milliseconds: 150),
); );
final hasUserProvidedInput = useState(
initialSearchText?.isNotEmpty == true,
);
useOnListenableChange(searchTextController, () {
hasUserProvidedInput.value = searchTextController.text.isNotEmpty;
});
final showNoInputSections =
!hasUserProvidedInput.value && searchTextController.text.isEmpty;
final searchFocusNode = useFocusNode(); final searchFocusNode = useFocusNode();
final textFieldKey = useMemoized(() => GlobalKey()); final textFieldKey = useMemoized(() => GlobalKey());
final preferredHeight = useState<double>(kToolbarHeight); final preferredHeight = useState<double>(kToolbarHeight);
@@ -484,86 +502,211 @@ class SearchScreen extends HookConsumerWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: ClipboardFillLink(controller: searchTextController), child: ClipboardFillLink(controller: searchTextController),
), ),
FullSearchTermSuggestions( if (showNoInputSections) ...[
searchTextController: searchTextController, HistoryHighlightsSection(
activeBang: activeBang, onUriSelected: (uri) async {
submitSearch: submitSearch, if (isEditMode) {
domain: isEditMode ? existingTabState.url.host : null, await ref
), .read(tabSessionProvider(tabId: tabId).notifier)
TabSearch(searchTextListenable: sampledSearchText), .loadUrl(url: uri);
BookmarkSearch( } else {
searchTextListenable: sampledSearchText, await ref
onUriSelected: (uri) async { .read(tabRepositoryProvider.notifier)
if (isEditMode) { .addTab(
await ref url: uri,
.read(tabSessionProvider(tabId: tabId).notifier) tabMode: effectiveTabMode,
.loadUrl(url: uri); parentId: (selectedTabType.value == TabType.child)
} else { ? ref.read(selectedTabProvider)
: null,
launchedFromIntent: launchedFromIntent,
selectTab: true,
containerSelection: selectedContainer == null
? const TabContainerSelection.unassigned()
: TabContainerSelection.specific(
selectedContainer,
),
);
}
if (context.mounted) {
ref
.read(bottomSheetControllerProvider.notifier)
.requestDismiss();
const BrowserRoute().go(context);
}
},
),
RecentFeedArticlesSection(
onArticleSelected: (article) {
FeedArticleRoute(articleId: article.id).go(context);
},
),
RecentTabsSection(
onTabSelected: (tabId) async {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab( .selectTab(tabId);
url: uri,
tabMode: effectiveTabMode, if (context.mounted) {
parentId: (selectedTabType.value == TabType.child) ref
? ref.read(selectedTabProvider) .read(bottomSheetControllerProvider.notifier)
: null, .requestDismiss();
launchedFromIntent: launchedFromIntent,
selectTab: true, const BrowserRoute().go(context);
containerSelection: selectedContainer == null }
? const TabContainerSelection.unassigned() },
: TabContainerSelection.specific( ),
selectedContainer, RecentHistorySection(
), onUriSelected: (uri) async {
if (isEditMode) {
await ref
.read(tabSessionProvider(tabId: tabId).notifier)
.loadUrl(url: uri);
} else {
await ref
.read(tabRepositoryProvider.notifier)
.addTab(
url: uri,
tabMode: effectiveTabMode,
parentId: (selectedTabType.value == TabType.child)
? ref.read(selectedTabProvider)
: null,
launchedFromIntent: launchedFromIntent,
selectTab: true,
containerSelection: selectedContainer == null
? const TabContainerSelection.unassigned()
: TabContainerSelection.specific(
selectedContainer,
),
);
}
if (context.mounted) {
ref
.read(bottomSheetControllerProvider.notifier)
.requestDismiss();
const BrowserRoute().go(context);
}
},
),
ContainersSection(
onContainerSelected: (container) async {
final result = await ref
.read(selectedContainerProvider.notifier)
.setContainerId(container.id);
if (!context.mounted) return;
if (result == SetContainerResult.successHasProxy) {
final shouldStartProxy = await ref
.read(startProxyControllerProvider.notifier)
.shouldPromptProxyStart();
if (context.mounted && shouldStartProxy) {
final dialogResult = await showDialog<bool>(
context: context,
builder: (_) => const TorDialog(),
); );
}
if (context.mounted) { if (dialogResult == true) {
ref await ref
.read(bottomSheetControllerProvider.notifier) .read(startProxyControllerProvider.notifier)
.requestDismiss(); .startProxy();
}
}
}
const BrowserRoute().go(context); if (context.mounted &&
} result != SetContainerResult.failed) {
}, const TabViewRoute().go(context);
), }
FeedSearch(searchTextNotifier: sampledSearchText), },
HistorySuggestions( ),
searchTextListenable: sampledSearchText, ],
onUriSelected: (uri) async { if (!showNoInputSections) ...[
if (isEditMode) { FullSearchTermSuggestions(
// Load into existing tab searchTextController: searchTextController,
await ref activeBang: activeBang,
.read(tabSessionProvider(tabId: tabId).notifier) submitSearch: submitSearch,
.loadUrl(url: uri); domain: isEditMode ? existingTabState.url.host : null,
} else { ),
// Create new tab TabSearch(searchTextListenable: sampledSearchText),
await ref BookmarkSearch(
.read(tabRepositoryProvider.notifier) searchTextListenable: sampledSearchText,
.addTab( onUriSelected: (uri) async {
url: uri, if (isEditMode) {
tabMode: effectiveTabMode, await ref
parentId: (selectedTabType.value == TabType.child) .read(tabSessionProvider(tabId: tabId).notifier)
? ref.read(selectedTabProvider) .loadUrl(url: uri);
: null, } else {
launchedFromIntent: launchedFromIntent, await ref
selectTab: true, .read(tabRepositoryProvider.notifier)
containerSelection: selectedContainer == null .addTab(
? const TabContainerSelection.unassigned() url: uri,
: TabContainerSelection.specific( tabMode: effectiveTabMode,
selectedContainer, parentId: (selectedTabType.value == TabType.child)
), ? ref.read(selectedTabProvider)
); : null,
} launchedFromIntent: launchedFromIntent,
selectTab: true,
containerSelection: selectedContainer == null
? const TabContainerSelection.unassigned()
: TabContainerSelection.specific(
selectedContainer,
),
);
}
if (context.mounted) { if (context.mounted) {
ref ref
.read(bottomSheetControllerProvider.notifier) .read(bottomSheetControllerProvider.notifier)
.requestDismiss(); .requestDismiss();
const BrowserRoute().go(context); const BrowserRoute().go(context);
} }
}, },
), ),
FeedSearch(searchTextNotifier: sampledSearchText),
HistorySuggestions(
searchTextListenable: sampledSearchText,
onUriSelected: (uri) async {
if (isEditMode) {
// Load into existing tab
await ref
.read(tabSessionProvider(tabId: tabId).notifier)
.loadUrl(url: uri);
} else {
// Create new tab
await ref
.read(tabRepositoryProvider.notifier)
.addTab(
url: uri,
tabMode: effectiveTabMode,
parentId: (selectedTabType.value == TabType.child)
? ref.read(selectedTabProvider)
: null,
launchedFromIntent: launchedFromIntent,
selectTab: true,
containerSelection: selectedContainer == null
? const TabContainerSelection.unassigned()
: TabContainerSelection.specific(
selectedContainer,
),
);
}
if (context.mounted) {
ref
.read(bottomSheetControllerProvider.notifier)
.requestDismiss();
const BrowserRoute().go(context);
}
},
),
],
], ],
), ),
), ),
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
class ContainersSection extends StatelessWidget {
final void Function(ContainerDataWithCount container) onContainerSelected;
const ContainersSection({super.key, required this.onContainerSelected});
@override
Widget build(BuildContext context) {
return SearchModuleSection(
title: 'Containers',
moduleType: SearchModuleType.containers,
totalCount: 1,
contentSliverBuilder:
({required bool isCollapsed, required int visibleCount}) => [
if (!isCollapsed)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ContainerChips(
selectedContainer: null,
onSelected: (container) {
if (container != null) {
onContainerSelected(container);
}
},
onDeleted: null,
displayMenu: false,
showUnassignedChip: false,
enableDragAndDrop: false,
),
),
),
],
);
}
}
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
import 'package:weblibre/presentation/widgets/url_list_tile.dart';
class HistoryHighlightsSection extends ConsumerWidget {
final void Function(Uri uri) onUriSelected;
const HistoryHighlightsSection({super.key, required this.onUriSelected});
@override
Widget build(BuildContext context, WidgetRef ref) {
final highlightsAsync = ref.watch(searchEmptyHistoryHighlightsProvider());
final highlights = highlightsAsync.value ?? [];
if (highlightsAsync.hasValue && highlights.isEmpty) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return SearchModuleSection(
title: 'History Highlights',
moduleType: SearchModuleType.historyHighlights,
totalCount: highlights.length,
contentSliverBuilder:
({required bool isCollapsed, required int visibleCount}) => [
if (!isCollapsed)
SliverList.builder(
itemCount: visibleCount,
itemBuilder: (context, index) {
final highlight = highlights[index];
final uri = Uri.parse(highlight.url);
return UrlListTile(
title: highlight.title ?? uri.authority,
uri: uri,
onTap: () => onUriSelected(uri),
);
},
),
],
);
}
}
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/providers/format.dart';
import 'package:weblibre/extensions/uri.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
import 'package:weblibre/features/web_feed/data/models/feed_link.dart';
import 'package:weblibre/features/web_feed/extensions/atom.dart';
import 'package:weblibre/features/web_feed/extensions/feed_article.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart';
class RecentFeedArticlesSection extends ConsumerWidget {
final void Function(FeedArticle article) onArticleSelected;
const RecentFeedArticlesSection({super.key, required this.onArticleSelected});
@override
Widget build(BuildContext context, WidgetRef ref) {
final articlesAsync = ref.watch(searchEmptyRecentFeedArticlesProvider());
final articles = articlesAsync.value ?? [];
if (articlesAsync.hasValue && articles.isEmpty) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return SearchModuleSection(
title: 'Recent Articles',
moduleType: SearchModuleType.recentArticles,
totalCount: articles.length,
contentSliverBuilder:
({required bool isCollapsed, required int visibleCount}) => [
if (!isCollapsed)
SliverList.builder(
itemCount: visibleCount,
itemBuilder: (context, index) {
final article = articles[index];
final articleDate = article.updated ?? article.created;
return ListTile(
leading: RepaintBoundary(
child: UrlIcon([
article.icon ??
article.links
?.getRelation(FeedLinkRelation.alternate)
?.uri ??
article.siteLink ??
article.feedId.base,
], iconSize: 24.0),
),
title: Text(
article.displayTitle,
style: Theme.of(context).textTheme.titleMedium,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (article.summaryPlain != null)
Text(
article.summaryPlain!,
style: Theme.of(context).textTheme.bodySmall,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
if (articleDate != null)
Align(
alignment: Alignment.topRight,
child: Text(
ref
.read(formatProvider.notifier)
.fullDateTime(articleDate),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(fontStyle: FontStyle.italic),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
onTap: () => onArticleSelected(article),
);
},
),
],
);
}
}
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
import 'package:weblibre/presentation/widgets/url_list_tile.dart';
class RecentHistorySection extends ConsumerWidget {
final void Function(Uri uri) onUriSelected;
const RecentHistorySection({super.key, required this.onUriSelected});
@override
Widget build(BuildContext context, WidgetRef ref) {
final historyAsync = ref.watch(searchEmptyRecentHistoryProvider());
final visits = historyAsync.value ?? [];
if (historyAsync.hasValue && visits.isEmpty) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return SearchModuleSection(
title: 'Recent History',
moduleType: SearchModuleType.recentHistory,
totalCount: visits.length,
contentSliverBuilder:
({required bool isCollapsed, required int visibleCount}) => [
if (!isCollapsed)
SliverList.builder(
itemCount: visibleCount,
itemBuilder: (context, index) {
final visit = visits[index];
final uri = Uri.parse(visit.url);
return UrlListTile(
title: visit.title ?? uri.authority,
uri: uri,
onTap: () => onUriSelected(uri),
);
},
),
],
);
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
import 'package:weblibre/presentation/widgets/url_list_tile.dart';
class RecentTabsSection extends ConsumerWidget {
final void Function(String tabId) onTabSelected;
const RecentTabsSection({super.key, required this.onTabSelected});
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabs = ref.watch(searchEmptyRecentTabsProvider());
if (tabs.isEmpty) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return SearchModuleSection(
title: 'Recent Tabs',
moduleType: SearchModuleType.recentTabs,
totalCount: tabs.length,
contentSliverBuilder:
({required bool isCollapsed, required int visibleCount}) => [
if (!isCollapsed)
SliverList.builder(
itemCount: visibleCount,
itemBuilder: (context, index) {
final (tabState, containerData) = tabs[index];
return UrlListTile(
title: tabState.titleOrAuthority,
uri: tabState.url,
leading: TabIcon(
tabState: tabState,
iconSize: UrlListTile.iconSize,
),
borderColor: containerData?.color,
onTap: () => onTabSelected(tabState.id),
);
},
),
],
);
}
}
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart';
class UrlListTile extends StatelessWidget {
final String title;
final Uri uri;
final Widget? leading;
final Widget? trailing;
final Color? borderColor;
final VoidCallback? onTap;
const UrlListTile({
super.key,
required this.title,
required this.uri,
this.leading,
this.trailing,
this.borderColor,
this.onTap,
});
static const iconSize = 32.0;
static const _borderRadius = BorderRadius.all(Radius.circular(12.0));
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme;
return Container(
margin: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 4.0),
decoration: BoxDecoration(
borderRadius: _borderRadius,
border: borderColor != null
? Border(right: BorderSide(color: borderColor!, width: 4.0))
: null,
),
child: Material(
color: Colors.transparent,
borderRadius: _borderRadius,
clipBehavior: Clip.antiAlias,
child: InkWell(
borderRadius: _borderRadius,
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(
left: 12.0,
top: 10.0,
bottom: 10.0,
right: 12.0,
),
child: Row(
children: [
leading ??
RepaintBoundary(
child: UrlIcon([uri], iconSize: iconSize),
),
const SizedBox(width: 14.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 3.0),
UriBreadcrumb(
uri: uri,
style: textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
if (trailing != null) ...[
const SizedBox(width: 8.0),
trailing!,
],
],
),
),
),
),
);
}
}
@@ -42,6 +42,8 @@ import mozilla.components.support.webextensions.WebExtensionSupport
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
private const val HISTORY_METADATA_MAX_AGE_IN_MS = 14L * 24 * 60 * 60 * 1000 // 14 days
object GlobalComponents { object GlobalComponents {
private var _components: Components? = null private var _components: Components? = null
private var currentMode: ComponentsMode? = null private var currentMode: ComponentsMode? = null
@@ -203,6 +205,10 @@ object GlobalComponents {
try { try {
GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage) GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage)
newComponents.core.historyMetadataService.cleanup(
System.currentTimeMillis() - HISTORY_METADATA_MAX_AGE_IN_MS,
)
GlobalAddonDependencyProvider.initialize( GlobalAddonDependencyProvider.initialize(
newComponents.core.addonManager, newComponents.core.addonManager,
newComponents.core.addonUpdater, newComponents.core.addonUpdater,
@@ -2,6 +2,8 @@ package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi
import eu.weblibre.flutter_mozilla_components.pigeons.HistoryHighlight
import eu.weblibre.flutter_mozilla_components.pigeons.HistoryHighlightWeights
import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo
import eu.weblibre.flutter_mozilla_components.pigeons.VisitType import eu.weblibre.flutter_mozilla_components.pigeons.VisitType
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -206,4 +208,32 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
} }
} }
} }
override fun getHistoryHighlights(
weights: HistoryHighlightWeights,
limit: Long,
callback: (Result<List<HistoryHighlight>>) -> Unit
) {
coroutineScope.launch {
withContext(Dispatchers.Main) {
val conceptWeights = mozilla.components.concept.storage.HistoryHighlightWeights(
viewTime = weights.viewTime,
frequency = weights.frequency,
)
val highlights = components.core.historyStorage.getHistoryHighlights(
conceptWeights,
limit.toInt(),
).map {
HistoryHighlight(
score = it.score,
placeId = it.placeId.toLong(),
url = it.url,
title = it.title,
previewImageUrl = it.previewImageUrl,
)
}
callback(Result.success(highlights))
}
}
}
} }
@@ -21,6 +21,8 @@ import eu.weblibre.flutter_mozilla_components.activities.NotificationActivity
import eu.weblibre.flutter_mozilla_components.R import eu.weblibre.flutter_mozilla_components.R
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
import eu.weblibre.flutter_mozilla_components.middleware.FlutterEventMiddleware import eu.weblibre.flutter_mozilla_components.middleware.FlutterEventMiddleware
import eu.weblibre.flutter_mozilla_components.middleware.HistoryMetadataMiddleware
import eu.weblibre.flutter_mozilla_components.middleware.HistoryMetadataService
import eu.weblibre.flutter_mozilla_components.middleware.SaveToPDFMiddleware import eu.weblibre.flutter_mozilla_components.middleware.SaveToPDFMiddleware
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
@@ -193,10 +195,15 @@ class Core(
FileUploadsDirCleaner { context.cacheDir } FileUploadsDirCleaner { context.cacheDir }
} }
val historyMetadataService by lazy {
HistoryMetadataService(storage = historyStorage)
}
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
val store by lazy { val store by lazy {
BrowserStore( BrowserStore(
middleware = listOf( middleware = listOf(
HistoryMetadataMiddleware(historyMetadataService),
FlutterEventMiddleware(flutterEvents), FlutterEventMiddleware(flutterEvents),
DownloadMiddleware(context, DownloadService::class.java, { false }), DownloadMiddleware(context, DownloadService::class.java, { false }),
ThumbnailsMiddleware(thumbnailStorage), ThumbnailsMiddleware(thumbnailStorage),
@@ -0,0 +1,114 @@
/*
* 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 https://mozilla.org/MPL/2.0/.
*/
package eu.weblibre.flutter_mozilla_components.middleware
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.HistoryMetadataAction
import mozilla.components.browser.state.action.MediaSessionAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.selector.findNormalTab
import mozilla.components.browser.state.selector.selectedNormalTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.Store
/**
* Records history metadata observations as the user browses.
*
* - On page load: records a [DocumentTypeObservation] so the page exists in metadata storage.
* - On tab switch / removal / URL change: records a [ViewTimeObservation] for the outgoing page.
*
* This is a simplified version of Fenix's HistoryMetadataMiddleware without search-group tracking.
*/
class HistoryMetadataMiddleware(
private val historyMetadataService: HistoryMetadataService,
) : Middleware<BrowserState, BrowserAction> {
override fun invoke(
store: Store<BrowserState, BrowserAction>,
next: (BrowserAction) -> Unit,
action: BrowserAction,
) {
// Pre-process: update view time for the currently selected tab before state changes.
when (action) {
is TabListAction.AddTabAction -> {
if (action.select) {
store.state.selectedNormalTab?.let { updateHistoryMetadata(it) }
}
}
is TabListAction.SelectTabAction -> {
store.state.selectedNormalTab?.let { updateHistoryMetadata(it) }
}
is TabListAction.RemoveTabAction -> {
if (action.tabId == store.state.selectedTabId) {
store.state.findNormalTab(action.tabId)?.let { updateHistoryMetadata(it) }
}
}
is TabListAction.RemoveTabsAction -> {
action.tabIds.find { it == store.state.selectedTabId }?.let {
store.state.findNormalTab(it)?.let { tab -> updateHistoryMetadata(tab) }
}
}
is ContentAction.UpdateUrlAction -> {
store.state.findNormalTab(action.sessionId)?.let { tab ->
if (tab.id == store.state.selectedTabId && action.url != tab.content.url) {
updateHistoryMetadata(tab)
}
}
}
else -> { /* no-op */ }
}
next(action)
// Post-process: create metadata for newly loaded pages (state is now up-to-date).
when (action) {
is TabListAction.AddTabAction -> {
if (!action.tab.content.private) {
createHistoryMetadataIfNeeded(store, action.tab)
}
}
is ContentAction.UpdateHistoryStateAction -> {
store.state.findNormalTab(action.sessionId)?.let { tab ->
createHistoryMetadataIfNeeded(store, tab)
}
}
is MediaSessionAction.UpdateMediaMetadataAction -> {
store.state.findNormalTab(action.tabId)?.let { tab ->
createHistoryMetadata(store, tab)
}
}
else -> { /* no-op */ }
}
}
private fun createHistoryMetadataIfNeeded(
store: Store<BrowserState, BrowserAction>,
tab: TabSessionState,
) {
val knownMetadata = tab.historyMetadata
if (knownMetadata == null || knownMetadata.url != tab.content.url) {
createHistoryMetadata(store, tab)
}
}
private fun createHistoryMetadata(
store: Store<BrowserState, BrowserAction>,
tab: TabSessionState,
) {
val key = historyMetadataService.createMetadata(tab)
store.dispatch(HistoryMetadataAction.SetHistoryMetadataKeyAction(tab.id, key))
}
private fun updateHistoryMetadata(tab: TabSessionState) {
tab.historyMetadata?.let {
historyMetadataService.updateMetadata(it, tab)
}
}
}
@@ -0,0 +1,78 @@
/*
* 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 https://mozilla.org/MPL/2.0/.
*/
package eu.weblibre.flutter_mozilla_components.middleware
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.concept.storage.DocumentType
import mozilla.components.concept.storage.HistoryMetadataKey
import mozilla.components.concept.storage.HistoryMetadataObservation
import mozilla.components.concept.storage.HistoryMetadataStorage
import mozilla.components.support.base.utils.NamedThreadFactory
import java.util.concurrent.Executors
class HistoryMetadataService(
private val storage: HistoryMetadataStorage,
private val scope: CoroutineScope = CoroutineScope(
Executors.newSingleThreadExecutor(
NamedThreadFactory("HistoryMetadataService"),
).asCoroutineDispatcher(),
),
) {
private val tabsLastUpdated = mutableMapOf<String, Long>()
fun createMetadata(tab: TabSessionState): HistoryMetadataKey {
val existingMetadata = tab.historyMetadata
val metadataKey = if (existingMetadata != null && existingMetadata.url == tab.content.url) {
existingMetadata
} else {
HistoryMetadataKey(url = tab.content.url)
}
val documentTypeObservation = HistoryMetadataObservation.DocumentTypeObservation(
documentType = when (tab.mediaSessionState) {
null -> DocumentType.Regular
else -> DocumentType.Media
},
)
scope.launch {
storage.noteHistoryMetadataObservation(metadataKey, documentTypeObservation)
}
return metadataKey
}
fun cleanup(olderThan: Long) {
scope.launch {
storage.deleteHistoryMetadataOlderThan(olderThan)
}
}
fun updateMetadata(key: HistoryMetadataKey, tab: TabSessionState) {
val now = System.currentTimeMillis()
val lastAccess = tab.lastAccess
if (lastAccess == 0L) {
return
}
scope.launch {
val lastUpdated = tabsLastUpdated[tab.id] ?: 0
if (lastUpdated > lastAccess) {
return@launch
}
val viewTimeObservation = HistoryMetadataObservation.ViewTimeObservation(
viewTime = (now - lastAccess).toInt(),
)
storage.noteHistoryMetadataObservation(key, viewTimeObservation)
tabsLastUpdated[tab.id] = now
}
}
}
@@ -1669,6 +1669,77 @@ data class VisitInfo (
override fun hashCode(): Int = toList().hashCode() override fun hashCode(): Int = toList().hashCode()
} }
/** Generated class from Pigeon that represents data sent in messages. */
data class HistoryHighlightWeights (
val viewTime: Double,
val frequency: Double
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): HistoryHighlightWeights {
val viewTime = pigeonVar_list[0] as Double
val frequency = pigeonVar_list[1] as Double
return HistoryHighlightWeights(viewTime, frequency)
}
}
fun toList(): List<Any?> {
return listOf(
viewTime,
frequency,
)
}
override fun equals(other: Any?): Boolean {
if (other !is HistoryHighlightWeights) {
return false
}
if (this === other) {
return true
}
return GeckoPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class HistoryHighlight (
val score: Double,
val placeId: Long,
val url: String,
val title: String? = null,
val previewImageUrl: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): HistoryHighlight {
val score = pigeonVar_list[0] as Double
val placeId = pigeonVar_list[1] as Long
val url = pigeonVar_list[2] as String
val title = pigeonVar_list[3] as String?
val previewImageUrl = pigeonVar_list[4] as String?
return HistoryHighlight(score, placeId, url, title, previewImageUrl)
}
}
fun toList(): List<Any?> {
return listOf(
score,
placeId,
url,
title,
previewImageUrl,
)
}
override fun equals(other: Any?): Boolean {
if (other !is HistoryHighlight) {
return false
}
if (this === other) {
return true
}
return GeckoPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */ /** Generated class from Pigeon that represents data sent in messages. */
data class HistoryItem ( data class HistoryItem (
val url: String, val url: String,
@@ -3985,235 +4056,245 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
} }
185.toByte() -> { 185.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
HistoryItem.fromList(it) HistoryHighlightWeights.fromList(it)
} }
} }
186.toByte() -> { 186.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
HistoryState.fromList(it) HistoryHighlight.fromList(it)
} }
} }
187.toByte() -> { 187.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ReaderableState.fromList(it) HistoryItem.fromList(it)
} }
} }
188.toByte() -> { 188.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SecurityInfoState.fromList(it) HistoryState.fromList(it)
} }
} }
189.toByte() -> { 189.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
TabContentState.fromList(it) ReaderableState.fromList(it)
} }
} }
190.toByte() -> { 190.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
FindResultState.fromList(it) SecurityInfoState.fromList(it)
} }
} }
191.toByte() -> { 191.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
CustomSelectionAction.fromList(it) TabContentState.fromList(it)
} }
} }
192.toByte() -> { 192.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
WebExtensionData.fromList(it) FindResultState.fromList(it)
} }
} }
193.toByte() -> { 193.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoSuggestion.fromList(it) CustomSelectionAction.fromList(it)
} }
} }
194.toByte() -> { 194.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
TabContent.fromList(it) WebExtensionData.fromList(it)
} }
} }
195.toByte() -> { 195.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ContentBlocking.fromList(it) GeckoSuggestion.fromList(it)
} }
} }
196.toByte() -> { 196.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
DohSettings.fromList(it) TabContent.fromList(it)
} }
} }
197.toByte() -> { 197.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoEngineSettings.fromList(it) ContentBlocking.fromList(it)
} }
} }
198.toByte() -> { 198.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
AutocompleteResult.fromList(it) DohSettings.fromList(it)
} }
} }
199.toByte() -> { 199.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
UnknownHitResult.fromList(it) GeckoEngineSettings.fromList(it)
} }
} }
200.toByte() -> { 200.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ImageHitResult.fromList(it) AutocompleteResult.fromList(it)
} }
} }
201.toByte() -> { 201.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
VideoHitResult.fromList(it) UnknownHitResult.fromList(it)
} }
} }
202.toByte() -> { 202.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
AudioHitResult.fromList(it) ImageHitResult.fromList(it)
} }
} }
203.toByte() -> { 203.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ImageSrcHitResult.fromList(it) VideoHitResult.fromList(it)
} }
} }
204.toByte() -> { 204.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
PhoneHitResult.fromList(it) AudioHitResult.fromList(it)
} }
} }
205.toByte() -> { 205.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
EmailHitResult.fromList(it) ImageSrcHitResult.fromList(it)
} }
} }
206.toByte() -> { 206.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeoHitResult.fromList(it) PhoneHitResult.fromList(it)
} }
} }
207.toByte() -> { 207.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
DownloadState.fromList(it) EmailHitResult.fromList(it)
} }
} }
208.toByte() -> { 208.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ShareInternetResourceState.fromList(it) GeoHitResult.fromList(it)
} }
} }
209.toByte() -> { 209.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
AddonCollection.fromList(it) DownloadState.fromList(it)
} }
} }
210.toByte() -> { 210.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SyncEngineStatus.fromList(it) ShareInternetResourceState.fromList(it)
} }
} }
211.toByte() -> { 211.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SyncAccountInfo.fromList(it) AddonCollection.fromList(it)
} }
} }
212.toByte() -> { 212.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SyncDevice.fromList(it) SyncEngineStatus.fromList(it)
} }
} }
213.toByte() -> { 213.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SyncIncomingTab.fromList(it) SyncAccountInfo.fromList(it)
} }
} }
214.toByte() -> { 214.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SyncRemoteTab.fromList(it) SyncDevice.fromList(it)
} }
} }
215.toByte() -> { 215.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SyncDeviceTabs.fromList(it) SyncIncomingTab.fromList(it)
} }
} }
216.toByte() -> { 216.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoPref.fromList(it) SyncRemoteTab.fromList(it)
} }
} }
217.toByte() -> { 217.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
MlProgressData.fromList(it) SyncDeviceTabs.fromList(it)
} }
} }
218.toByte() -> { 218.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ContainerSiteAssignment.fromList(it) GeckoPref.fromList(it)
} }
} }
219.toByte() -> { 219.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoHeader.fromList(it) MlProgressData.fromList(it)
} }
} }
220.toByte() -> { 220.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoFetchRequest.fromList(it) ContainerSiteAssignment.fromList(it)
} }
} }
221.toByte() -> { 221.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoFetchResponse.fromList(it) GeckoHeader.fromList(it)
} }
} }
222.toByte() -> { 222.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
BookmarkNode.fromList(it) GeckoFetchRequest.fromList(it)
} }
} }
223.toByte() -> { 223.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
BookmarkInfo.fromList(it) GeckoFetchResponse.fromList(it)
} }
} }
224.toByte() -> { 224.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SitePermissions.fromList(it) BookmarkNode.fromList(it)
} }
} }
225.toByte() -> { 225.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
TrackingProtectionException.fromList(it) BookmarkInfo.fromList(it)
} }
} }
226.toByte() -> { 226.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
PwaIcon.fromList(it) SitePermissions.fromList(it)
} }
} }
227.toByte() -> { 227.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ShareTargetFiles.fromList(it) TrackingProtectionException.fromList(it)
} }
} }
228.toByte() -> { 228.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ShareTargetParams.fromList(it) PwaIcon.fromList(it)
} }
} }
229.toByte() -> { 229.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ShareTarget.fromList(it) ShareTargetFiles.fromList(it)
} }
} }
230.toByte() -> { 230.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ExternalApplicationResource.fromList(it) ShareTargetParams.fromList(it)
} }
} }
231.toByte() -> { 231.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ShareTarget.fromList(it)
}
}
232.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ExternalApplicationResource.fromList(it)
}
}
233.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
PwaManifest.fromList(it) PwaManifest.fromList(it)
} }
@@ -4447,194 +4528,202 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
stream.write(184) stream.write(184)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is HistoryItem -> { is HistoryHighlightWeights -> {
stream.write(185) stream.write(185)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is HistoryState -> { is HistoryHighlight -> {
stream.write(186) stream.write(186)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ReaderableState -> { is HistoryItem -> {
stream.write(187) stream.write(187)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SecurityInfoState -> { is HistoryState -> {
stream.write(188) stream.write(188)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TabContentState -> { is ReaderableState -> {
stream.write(189) stream.write(189)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is FindResultState -> { is SecurityInfoState -> {
stream.write(190) stream.write(190)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is CustomSelectionAction -> { is TabContentState -> {
stream.write(191) stream.write(191)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is WebExtensionData -> { is FindResultState -> {
stream.write(192) stream.write(192)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoSuggestion -> { is CustomSelectionAction -> {
stream.write(193) stream.write(193)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TabContent -> { is WebExtensionData -> {
stream.write(194) stream.write(194)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ContentBlocking -> { is GeckoSuggestion -> {
stream.write(195) stream.write(195)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is DohSettings -> { is TabContent -> {
stream.write(196) stream.write(196)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoEngineSettings -> { is ContentBlocking -> {
stream.write(197) stream.write(197)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is AutocompleteResult -> { is DohSettings -> {
stream.write(198) stream.write(198)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is UnknownHitResult -> { is GeckoEngineSettings -> {
stream.write(199) stream.write(199)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ImageHitResult -> { is AutocompleteResult -> {
stream.write(200) stream.write(200)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is VideoHitResult -> { is UnknownHitResult -> {
stream.write(201) stream.write(201)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is AudioHitResult -> { is ImageHitResult -> {
stream.write(202) stream.write(202)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ImageSrcHitResult -> { is VideoHitResult -> {
stream.write(203) stream.write(203)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is PhoneHitResult -> { is AudioHitResult -> {
stream.write(204) stream.write(204)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is EmailHitResult -> { is ImageSrcHitResult -> {
stream.write(205) stream.write(205)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeoHitResult -> { is PhoneHitResult -> {
stream.write(206) stream.write(206)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is DownloadState -> { is EmailHitResult -> {
stream.write(207) stream.write(207)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ShareInternetResourceState -> { is GeoHitResult -> {
stream.write(208) stream.write(208)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is AddonCollection -> { is DownloadState -> {
stream.write(209) stream.write(209)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SyncEngineStatus -> { is ShareInternetResourceState -> {
stream.write(210) stream.write(210)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SyncAccountInfo -> { is AddonCollection -> {
stream.write(211) stream.write(211)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SyncDevice -> { is SyncEngineStatus -> {
stream.write(212) stream.write(212)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SyncIncomingTab -> { is SyncAccountInfo -> {
stream.write(213) stream.write(213)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SyncRemoteTab -> { is SyncDevice -> {
stream.write(214) stream.write(214)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SyncDeviceTabs -> { is SyncIncomingTab -> {
stream.write(215) stream.write(215)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoPref -> { is SyncRemoteTab -> {
stream.write(216) stream.write(216)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is MlProgressData -> { is SyncDeviceTabs -> {
stream.write(217) stream.write(217)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ContainerSiteAssignment -> { is GeckoPref -> {
stream.write(218) stream.write(218)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoHeader -> { is MlProgressData -> {
stream.write(219) stream.write(219)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoFetchRequest -> { is ContainerSiteAssignment -> {
stream.write(220) stream.write(220)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoFetchResponse -> { is GeckoHeader -> {
stream.write(221) stream.write(221)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is BookmarkNode -> { is GeckoFetchRequest -> {
stream.write(222) stream.write(222)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is BookmarkInfo -> { is GeckoFetchResponse -> {
stream.write(223) stream.write(223)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SitePermissions -> { is BookmarkNode -> {
stream.write(224) stream.write(224)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TrackingProtectionException -> { is BookmarkInfo -> {
stream.write(225) stream.write(225)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is PwaIcon -> { is SitePermissions -> {
stream.write(226) stream.write(226)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ShareTargetFiles -> { is TrackingProtectionException -> {
stream.write(227) stream.write(227)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ShareTargetParams -> { is PwaIcon -> {
stream.write(228) stream.write(228)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ShareTarget -> { is ShareTargetFiles -> {
stream.write(229) stream.write(229)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ExternalApplicationResource -> { is ShareTargetParams -> {
stream.write(230) stream.write(230)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is PwaManifest -> { is ShareTarget -> {
stream.write(231) stream.write(231)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ExternalApplicationResource -> {
stream.write(232)
writeValue(stream, value.toList())
}
is PwaManifest -> {
stream.write(233)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value) else -> super.writeValue(stream, value)
} }
} }
@@ -7531,6 +7620,7 @@ interface GeckoHistoryApi {
fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit) fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit)
fun deleteDownload(id: String, callback: (Result<Unit>) -> Unit) fun deleteDownload(id: String, callback: (Result<Unit>) -> Unit)
fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit) fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit)
fun getHistoryHighlights(weights: HistoryHighlightWeights, limit: Long, callback: (Result<List<HistoryHighlight>>) -> Unit)
companion object { companion object {
/** The codec used by GeckoHistoryApi. */ /** The codec used by GeckoHistoryApi. */
@@ -7644,6 +7734,27 @@ interface GeckoHistoryApi {
channel.setMessageHandler(null) channel.setMessageHandler(null)
} }
} }
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.getHistoryHighlights$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val weightsArg = args[0] as HistoryHighlightWeights
val limitArg = args[1] as Long
api.getHistoryHighlights(weightsArg, limitArg) { result: Result<List<HistoryHighlight>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(GeckoPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
} }
} }
} }
@@ -65,6 +65,8 @@ export 'src/pigeons/gecko.g.dart'
GeckoSuggestionType, GeckoSuggestionType,
GeckoTrackingProtectionApi, GeckoTrackingProtectionApi,
GeoHitResult, GeoHitResult,
HistoryHighlight,
HistoryHighlightWeights,
HistoryMetadataKey, HistoryMetadataKey,
HitResult, HitResult,
HttpsOnlyMode, HttpsOnlyMode,
@@ -48,8 +48,7 @@ class GeckoEventService extends GeckoStateEvents {
final _tabAddedSubject = PublishSubject<String>(); final _tabAddedSubject = PublishSubject<String>();
final _mlProgressSubject = PublishSubject<MlProgressData>(); final _mlProgressSubject = PublishSubject<MlProgressData>();
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>(); final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
final _translationEngineSubject = final _translationEngineSubject = BehaviorSubject<TranslationEngineEvent>();
BehaviorSubject<TranslationEngineEvent>();
final _tabTranslationSubject = ReplaySubject<TabTranslationEvent>(); final _tabTranslationSubject = ReplaySubject<TabTranslationEvent>();
// Event streams // Event streams
@@ -60,4 +60,11 @@ class GeckoHistoryService {
end.millisecondsSinceEpoch, end.millisecondsSinceEpoch,
); );
} }
Future<List<HistoryHighlight>> getHistoryHighlights({
required HistoryHighlightWeights weights,
required int limit,
}) {
return _api.getHistoryHighlights(weights, limit);
}
} }
File diff suppressed because it is too large Load Diff
@@ -534,6 +534,29 @@ class VisitInfo {
); );
} }
class HistoryHighlightWeights {
final double viewTime;
final double frequency;
HistoryHighlightWeights(this.viewTime, this.frequency);
}
class HistoryHighlight {
final double score;
final int placeId;
final String url;
final String? title;
final String? previewImageUrl;
HistoryHighlight(
this.score,
this.placeId,
this.url,
this.title,
this.previewImageUrl,
);
}
class HistoryItem { class HistoryItem {
final String url; final String url;
final String title; final String title;
@@ -1639,10 +1662,7 @@ abstract class GeckoStateEvents {
int sequence, int sequence,
TranslationEngineStateData state, TranslationEngineStateData state,
); );
void onTabTranslationStateChange( void onTabTranslationStateChange(int sequence, TabTranslationStateData state);
int sequence,
TabTranslationStateData state,
);
} }
@FlutterApi() @FlutterApi()
@@ -1797,6 +1817,12 @@ abstract class GeckoHistoryApi {
@async @async
void deleteVisitsBetween(int startMillis, int endMillis); void deleteVisitsBetween(int startMillis, int endMillis);
@async
List<HistoryHighlight> getHistoryHighlights(
HistoryHighlightWeights weights,
int limit,
);
} }
@HostApi() @HostApi()
+112 -62
View File
@@ -15,7 +15,11 @@ PlatformException _createConnectionError(String channelName) {
); );
} }
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) { List<Object?> wrapResponse({
Object? result,
PlatformException? error,
bool empty = false,
}) {
if (empty) { if (empty) {
return <Object?>[]; return <Object?>[];
} }
@@ -24,37 +28,48 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
} }
return <Object?>[error.code, error.message, error.details]; return <Object?>[error.code, error.message, error.details];
} }
bool _deepEquals(Object? a, Object? b) { bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) { if (a is List && b is List) {
return a.length == b.length && return a.length == b.length &&
a.indexed a.indexed.every(
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); ((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
);
} }
if (a is Map && b is Map) { if (a is Map && b is Map) {
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) => return a.length == b.length &&
(b as Map<Object?, Object?>).containsKey(entry.key) && a.entries.every(
_deepEquals(entry.value, b[entry.key])); (MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]),
);
} }
return a == b; return a == b;
} }
/// Transport types for Tor connections /// Transport types for Tor connections
enum TransportType { enum TransportType {
/// Direct Tor connection (no bridges) /// Direct Tor connection (no bridges)
none, none,
/// obfs4 pluggable transport /// obfs4 pluggable transport
obfs4, obfs4,
/// Snowflake pluggable transport (default broker) /// Snowflake pluggable transport (default broker)
snowflake, snowflake,
/// Snowflake via AMP cache /// Snowflake via AMP cache
snowflakeAmp, snowflakeAmp,
/// Meek pluggable transport /// Meek pluggable transport
meek, meek,
/// Meek via Azure CDN /// Meek via Azure CDN
meekAzure, meekAzure,
/// WebTunnel pluggable transport /// WebTunnel pluggable transport
webtunnel, webtunnel,
/// Custom bridge lines (passthrough) /// Custom bridge lines (passthrough)
custom, custom,
} }
@@ -95,7 +110,8 @@ class TorConfiguration {
} }
Object encode() { Object encode() {
return _toList(); } return _toList();
}
static TorConfiguration decode(Object result) { static TorConfiguration decode(Object result) {
result as List<Object?>; result as List<Object?>;
@@ -122,8 +138,7 @@ class TorConfiguration {
@override @override
// ignore: avoid_equals_and_hash_code_on_mutable_classes // ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList()) int get hashCode => Object.hashAll(_toList());
;
} }
/// Current Tor status /// Current Tor status
@@ -162,7 +177,8 @@ class TorStatus {
} }
Object encode() { Object encode() {
return _toList(); } return _toList();
}
static TorStatus decode(Object result) { static TorStatus decode(Object result) {
result as List<Object?>; result as List<Object?>;
@@ -189,8 +205,7 @@ class TorStatus {
@override @override
// ignore: avoid_equals_and_hash_code_on_mutable_classes // ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList()) int get hashCode => Object.hashAll(_toList());
;
} }
/// Log message from Tor /// Log message from Tor
@@ -211,15 +226,12 @@ class TorLogMessage {
int timestamp; int timestamp;
List<Object?> _toList() { List<Object?> _toList() {
return <Object?>[ return <Object?>[severity, message, timestamp];
severity,
message,
timestamp,
];
} }
Object encode() { Object encode() {
return _toList(); } return _toList();
}
static TorLogMessage decode(Object result) { static TorLogMessage decode(Object result) {
result as List<Object?>; result as List<Object?>;
@@ -244,11 +256,9 @@ class TorLogMessage {
@override @override
// ignore: avoid_equals_and_hash_code_on_mutable_classes // ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList()) int get hashCode => Object.hashAll(_toList());
;
} }
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@override @override
@@ -256,16 +266,16 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) { if (value is int) {
buffer.putUint8(4); buffer.putUint8(4);
buffer.putInt64(value); buffer.putInt64(value);
} else if (value is TransportType) { } else if (value is TransportType) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.index); writeValue(buffer, value.index);
} else if (value is TorConfiguration) { } else if (value is TorConfiguration) {
buffer.putUint8(130); buffer.putUint8(130);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is TorStatus) { } else if (value is TorStatus) {
buffer.putUint8(131); buffer.putUint8(131);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is TorLogMessage) { } else if (value is TorLogMessage) {
buffer.putUint8(132); buffer.putUint8(132);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else { } else {
@@ -276,14 +286,14 @@ class _PigeonCodec extends StandardMessageCodec {
@override @override
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 129: case 129:
final value = readValue(buffer) as int?; final value = readValue(buffer) as int?;
return value == null ? null : TransportType.values[value]; return value == null ? null : TransportType.values[value];
case 130: case 130:
return TorConfiguration.decode(readValue(buffer)!); return TorConfiguration.decode(readValue(buffer)!);
case 131: case 131:
return TorStatus.decode(readValue(buffer)!); return TorStatus.decode(readValue(buffer)!);
case 132: case 132:
return TorLogMessage.decode(readValue(buffer)!); return TorLogMessage.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@@ -297,8 +307,10 @@ class TorApi {
/// available for dependency injection. If it is left null, the default /// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform. /// BinaryMessenger will be used which routes to the host platform.
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger, : pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
final BinaryMessenger? pigeonVar_binaryMessenger; final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
@@ -308,13 +320,16 @@ class TorApi {
/// Start Tor with the given configuration /// Start Tor with the given configuration
/// Returns a Future to avoid blocking the main thread /// Returns a Future to avoid blocking the main thread
Future<int> startTor(TorConfiguration config) async { Future<int> startTor(TorConfiguration config) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[config]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
<Object?>[config],
);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@@ -336,7 +351,8 @@ class TorApi {
/// Stop Tor /// Stop Tor
Future<void> stopTor() async { Future<void> stopTor() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
@@ -359,7 +375,8 @@ class TorApi {
/// Get current status /// Get current status
Future<TorStatus> getStatus() async { Future<TorStatus> getStatus() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
@@ -387,7 +404,8 @@ class TorApi {
/// Request a new Tor identity (new circuit) /// Request a new Tor identity (new circuit)
Future<void> requestNewIdentity() async { Future<void> requestNewIdentity() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
@@ -419,54 +437,76 @@ abstract class TorLogApi {
/// Called when status changes /// Called when status changes
void onStatusChanged(TorStatus status); void onStatusChanged(TorStatus status);
static void setUp(TorLogApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { static void setUp(
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; TorLogApi? api, {
BinaryMessenger? binaryMessenger,
String messageChannelSuffix = '',
}) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
{ {
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix', pigeonChannelCodec, 'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix',
binaryMessenger: binaryMessenger); pigeonChannelCodec,
binaryMessenger: binaryMessenger,
);
if (api == null) { if (api == null) {
pigeonVar_channel.setMessageHandler(null); pigeonVar_channel.setMessageHandler(null);
} else { } else {
pigeonVar_channel.setMessageHandler((Object? message) async { pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null, assert(
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.'); message != null,
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.',
);
final List<Object?> args = (message as List<Object?>?)!; final List<Object?> args = (message as List<Object?>?)!;
final TorLogMessage? arg_log = (args[0] as TorLogMessage?); final TorLogMessage? arg_log = (args[0] as TorLogMessage?);
assert(arg_log != null, assert(
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.'); arg_log != null,
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.',
);
try { try {
api.onLogMessage(arg_log!); api.onLogMessage(arg_log!);
return wrapResponse(empty: true); return wrapResponse(empty: true);
} on PlatformException catch (e) { } on PlatformException catch (e) {
return wrapResponse(error: e); return wrapResponse(error: e);
} catch (e) { } catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()),
);
} }
}); });
} }
} }
{ {
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix', pigeonChannelCodec, 'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix',
binaryMessenger: binaryMessenger); pigeonChannelCodec,
binaryMessenger: binaryMessenger,
);
if (api == null) { if (api == null) {
pigeonVar_channel.setMessageHandler(null); pigeonVar_channel.setMessageHandler(null);
} else { } else {
pigeonVar_channel.setMessageHandler((Object? message) async { pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null, assert(
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.'); message != null,
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.',
);
final List<Object?> args = (message as List<Object?>?)!; final List<Object?> args = (message as List<Object?>?)!;
final TorStatus? arg_status = (args[0] as TorStatus?); final TorStatus? arg_status = (args[0] as TorStatus?);
assert(arg_status != null, assert(
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.'); arg_status != null,
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.',
);
try { try {
api.onStatusChanged(arg_status!); api.onStatusChanged(arg_status!);
return wrapResponse(empty: true); return wrapResponse(empty: true);
} on PlatformException catch (e) { } on PlatformException catch (e) {
return wrapResponse(error: e); return wrapResponse(error: e);
} catch (e) { } catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()),
);
} }
}); });
} }
@@ -478,9 +518,13 @@ class IPtProxyController {
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is /// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default /// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform. /// BinaryMessenger will be used which routes to the host platform.
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) IPtProxyController({
: pigeonVar_binaryMessenger = binaryMessenger, BinaryMessenger? binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; String messageChannelSuffix = '',
}) : pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
final BinaryMessenger? pigeonVar_binaryMessenger; final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
@@ -488,13 +532,16 @@ class IPtProxyController {
final String pigeonVar_messageChannelSuffix; final String pigeonVar_messageChannelSuffix;
Future<int> start(TransportType proxyType, String proxy) async { Future<int> start(TransportType proxyType, String proxy) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType, proxy]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
<Object?>[proxyType, proxy],
);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@@ -515,13 +562,16 @@ class IPtProxyController {
} }
Future<void> stop(TransportType proxyType) async { Future<void> stop(TransportType proxyType) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
<Object?>[proxyType],
);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@@ -14,40 +14,39 @@ PlatformException _createConnectionError(String channelName) {
message: 'Unable to establish connection on channel: "$channelName".', message: 'Unable to establish connection on channel: "$channelName".',
); );
} }
bool _deepEquals(Object? a, Object? b) { bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) { if (a is List && b is List) {
return a.length == b.length && return a.length == b.length &&
a.indexed a.indexed.every(
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); ((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
);
} }
if (a is Map && b is Map) { if (a is Map && b is Map) {
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) => return a.length == b.length &&
(b as Map<Object?, Object?>).containsKey(entry.key) && a.entries.every(
_deepEquals(entry.value, b[entry.key])); (MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]),
);
} }
return a == b; return a == b;
} }
class LocalizedResult { class LocalizedResult {
LocalizedResult({ LocalizedResult({required this.languageName, this.countryName});
required this.languageName,
this.countryName,
});
String languageName; String languageName;
String? countryName; String? countryName;
List<Object?> _toList() { List<Object?> _toList() {
return <Object?>[ return <Object?>[languageName, countryName];
languageName,
countryName,
];
} }
Object encode() { Object encode() {
return _toList(); } return _toList();
}
static LocalizedResult decode(Object result) { static LocalizedResult decode(Object result) {
result as List<Object?>; result as List<Object?>;
@@ -71,11 +70,9 @@ class LocalizedResult {
@override @override
// ignore: avoid_equals_and_hash_code_on_mutable_classes // ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList()) int get hashCode => Object.hashAll(_toList());
;
} }
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@override @override
@@ -83,7 +80,7 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) { if (value is int) {
buffer.putUint8(4); buffer.putUint8(4);
buffer.putInt64(value); buffer.putInt64(value);
} else if (value is LocalizedResult) { } else if (value is LocalizedResult) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else { } else {
@@ -94,7 +91,7 @@ class _PigeonCodec extends StandardMessageCodec {
@override @override
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 129: case 129:
return LocalizedResult.decode(readValue(buffer)!); return LocalizedResult.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@@ -106,23 +103,33 @@ class LocaleResolver {
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is /// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default /// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform. /// BinaryMessenger will be used which routes to the host platform.
LocaleResolver({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) LocaleResolver({
: pigeonVar_binaryMessenger = binaryMessenger, BinaryMessenger? binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; String messageChannelSuffix = '',
}) : pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
final BinaryMessenger? pigeonVar_binaryMessenger; final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix; final String pigeonVar_messageChannelSuffix;
Future<LocalizedResult> resolve(String languageTag, String targetLangouageTag) async { Future<LocalizedResult> resolve(
final pigeonVar_channelName = 'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix'; String languageTag,
String targetLangouageTag,
) async {
final pigeonVar_channelName =
'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[languageTag, targetLangouageTag]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
<Object?>[languageTag, targetLangouageTag],
);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@@ -8,7 +8,11 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) { List<Object?> wrapResponse({
Object? result,
PlatformException? error,
bool empty = false,
}) {
if (empty) { if (empty) {
return <Object?>[]; return <Object?>[];
} }
@@ -17,21 +21,25 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
} }
return <Object?>[error.code, error.message, error.details]; return <Object?>[error.code, error.message, error.details];
} }
bool _deepEquals(Object? a, Object? b) { bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) { if (a is List && b is List) {
return a.length == b.length && return a.length == b.length &&
a.indexed a.indexed.every(
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); ((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
);
} }
if (a is Map && b is Map) { if (a is Map && b is Map) {
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) => return a.length == b.length &&
(b as Map<Object?, Object?>).containsKey(entry.key) && a.entries.every(
_deepEquals(entry.value, b[entry.key])); (MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]),
);
} }
return a == b; return a == b;
} }
class Intent { class Intent {
Intent({ Intent({
this.fromPackageName, this.fromPackageName,
@@ -66,7 +74,8 @@ class Intent {
} }
Object encode() { Object encode() {
return _toList(); } return _toList();
}
static Intent decode(Object result) { static Intent decode(Object result) {
result as List<Object?>; result as List<Object?>;
@@ -94,11 +103,9 @@ class Intent {
@override @override
// ignore: avoid_equals_and_hash_code_on_mutable_classes // ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList()) int get hashCode => Object.hashAll(_toList());
;
} }
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@override @override
@@ -106,7 +113,7 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) { if (value is int) {
buffer.putUint8(4); buffer.putUint8(4);
buffer.putInt64(value); buffer.putInt64(value);
} else if (value is Intent) { } else if (value is Intent) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else { } else {
@@ -117,7 +124,7 @@ class _PigeonCodec extends StandardMessageCodec {
@override @override
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 129: case 129:
return Intent.decode(readValue(buffer)!); return Intent.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@@ -130,32 +137,48 @@ abstract class IntentEvents {
void onIntentReceived(int sequence, Intent intent); void onIntentReceived(int sequence, Intent intent);
static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { static void setUp(
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; IntentEvents? api, {
BinaryMessenger? binaryMessenger,
String messageChannelSuffix = '',
}) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
{ {
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix', pigeonChannelCodec, 'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix',
binaryMessenger: binaryMessenger); pigeonChannelCodec,
binaryMessenger: binaryMessenger,
);
if (api == null) { if (api == null) {
pigeonVar_channel.setMessageHandler(null); pigeonVar_channel.setMessageHandler(null);
} else { } else {
pigeonVar_channel.setMessageHandler((Object? message) async { pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null, assert(
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.'); message != null,
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.',
);
final List<Object?> args = (message as List<Object?>?)!; final List<Object?> args = (message as List<Object?>?)!;
final int? arg_sequence = (args[0] as int?); final int? arg_sequence = (args[0] as int?);
assert(arg_sequence != null, assert(
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.'); arg_sequence != null,
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.',
);
final Intent? arg_intent = (args[1] as Intent?); final Intent? arg_intent = (args[1] as Intent?);
assert(arg_intent != null, assert(
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.'); arg_intent != null,
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.',
);
try { try {
api.onIntentReceived(arg_sequence!, arg_intent!); api.onIntentReceived(arg_sequence!, arg_intent!);
return wrapResponse(empty: true); return wrapResponse(empty: true);
} on PlatformException catch (e) { } on PlatformException catch (e) {
return wrapResponse(error: e); return wrapResponse(error: e);
} catch (e) { } catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()),
);
} }
}); });
} }
@@ -15,7 +15,11 @@ PlatformException _createConnectionError(String channelName) {
); );
} }
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) { List<Object?> wrapResponse({
Object? result,
PlatformException? error,
bool empty = false,
}) {
if (empty) { if (empty) {
return <Object?>[]; return <Object?>[];
} }
@@ -25,7 +29,6 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
return <Object?>[error.code, error.message, error.details]; return <Object?>[error.code, error.message, error.details];
} }
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@override @override
@@ -52,9 +55,13 @@ class SpeechToTextApi {
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is /// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default /// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform. /// BinaryMessenger will be used which routes to the host platform.
SpeechToTextApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) SpeechToTextApi({
: pigeonVar_binaryMessenger = binaryMessenger, BinaryMessenger? binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; String messageChannelSuffix = '',
}) : pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
final BinaryMessenger? pigeonVar_binaryMessenger; final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
@@ -69,13 +76,16 @@ class SpeechToTextApi {
/// The [locale] parameter specifies the language locale for recognition /// The [locale] parameter specifies the language locale for recognition
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default. /// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
Future<bool> showDialog({String? locale}) async { Future<bool> showDialog({String? locale}) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix'; final pigeonVar_channelName =
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[locale]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
<Object?>[locale],
);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@@ -106,29 +116,43 @@ abstract class SpeechToTextEvents {
/// recognition failed or was cancelled. /// recognition failed or was cancelled.
void onTextReceived(String text); void onTextReceived(String text);
static void setUp(SpeechToTextEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { static void setUp(
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; SpeechToTextEvents? api, {
BinaryMessenger? binaryMessenger,
String messageChannelSuffix = '',
}) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty
? '.$messageChannelSuffix'
: '';
{ {
final pigeonVar_channel = BasicMessageChannel<Object?>( final pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix', pigeonChannelCodec, 'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix',
binaryMessenger: binaryMessenger); pigeonChannelCodec,
binaryMessenger: binaryMessenger,
);
if (api == null) { if (api == null) {
pigeonVar_channel.setMessageHandler(null); pigeonVar_channel.setMessageHandler(null);
} else { } else {
pigeonVar_channel.setMessageHandler((Object? message) async { pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null, assert(
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.'); message != null,
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.',
);
final List<Object?> args = (message as List<Object?>?)!; final List<Object?> args = (message as List<Object?>?)!;
final String? arg_text = (args[0] as String?); final String? arg_text = (args[0] as String?);
assert(arg_text != null, assert(
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.'); arg_text != null,
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.',
);
try { try {
api.onTextReceived(arg_text!); api.onTextReceived(arg_text!);
return wrapResponse(empty: true); return wrapResponse(empty: true);
} on PlatformException catch (e) { } on PlatformException catch (e) {
return wrapResponse(error: e); return wrapResponse(error: e);
} catch (e) { } catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()),
);
} }
}); });
} }