added many suggestion modules
This commit is contained in:
@@ -66,8 +66,7 @@ class TranslationState with FastEquatable {
|
||||
displayError: data.displayError,
|
||||
);
|
||||
|
||||
bool get hasError =>
|
||||
translationErrorName != null && (displayError ?? true);
|
||||
bool get hasError => translationErrorName != null && (displayError ?? true);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
|
||||
@@ -136,6 +136,7 @@ class BookmarkSearchResults extends _$BookmarkSearchResults {
|
||||
}
|
||||
|
||||
final results = await _service.searchBookmarks(query, limit: limit);
|
||||
if (!ref.mounted) return;
|
||||
state = results
|
||||
.map(BookmarkItem.parseRecursive)
|
||||
.whereType<BookmarkEntry>()
|
||||
|
||||
@@ -86,7 +86,7 @@ final class BookmarkSearchResultsProvider
|
||||
}
|
||||
|
||||
String _$bookmarkSearchResultsHash() =>
|
||||
r'f1413466db26c1e3407501453b8cec97504a771d';
|
||||
r'44af94b2234c817b08b0bde2dc68f9ab8d2d0a9a';
|
||||
|
||||
abstract class _$BookmarkSearchResults extends $Notifier<List<BookmarkEntry>> {
|
||||
List<BookmarkEntry> build();
|
||||
|
||||
@@ -47,7 +47,7 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
typedef TabStateWirthContainer = (TabState, ContainerData?);
|
||||
typedef TabStateWithContainer = (TabState, ContainerData?);
|
||||
|
||||
@Riverpod()
|
||||
bool canManualTabReorder(Ref ref) {
|
||||
@@ -175,7 +175,7 @@ EquatableValue<Map<String, TabState>> containerTabStates(
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> fifoTabStates(Ref ref) {
|
||||
final containerData = ref
|
||||
.watch(watchContainersWithCountProvider.select((value) => value.value))
|
||||
.mapNotNull(
|
||||
@@ -202,7 +202,7 @@ EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
selectedContainerTabStatesWithContainer(Ref ref) {
|
||||
final filter = ref.watch(
|
||||
selectedContainerProvider.select(
|
||||
@@ -256,7 +256,7 @@ selectedContainerTabStatesWithContainer(Ref ref) {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableValue<List<TabStateWirthContainer>> quickTabSwitcherTabStates(
|
||||
EquatableValue<List<TabStateWithContainer>> quickTabSwitcherTabStates(
|
||||
Ref ref,
|
||||
QuickTabSwitcherMode mode,
|
||||
) {
|
||||
|
||||
@@ -430,11 +430,11 @@ final fifoTabStatesProvider = FifoTabStatesProvider._();
|
||||
final class FifoTabStatesProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
>
|
||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
||||
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||
FifoTabStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -451,30 +451,30 @@ final class FifoTabStatesProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
||||
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||
return fifoTabStates(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(
|
||||
EquatableValue<List<TabStateWirthContainer>> value,
|
||||
EquatableValue<List<TabStateWithContainer>> value,
|
||||
) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$fifoTabStatesHash() => r'03298f30559d55e986d97a17d517b9a9abb89be4';
|
||||
String _$fifoTabStatesHash() => r'f8dc69382d307bc83193a3b839a16313e9c1ab69';
|
||||
|
||||
@ProviderFor(selectedContainerTabStatesWithContainer)
|
||||
final selectedContainerTabStatesWithContainerProvider =
|
||||
@@ -483,11 +483,11 @@ final selectedContainerTabStatesWithContainerProvider =
|
||||
final class SelectedContainerTabStatesWithContainerProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
>
|
||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
||||
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||
SelectedContainerTabStatesWithContainerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -505,23 +505,23 @@ final class SelectedContainerTabStatesWithContainerProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
||||
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||
return selectedContainerTabStatesWithContainer(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(
|
||||
EquatableValue<List<TabStateWirthContainer>> value,
|
||||
EquatableValue<List<TabStateWithContainer>> value,
|
||||
) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
@@ -529,7 +529,7 @@ final class SelectedContainerTabStatesWithContainerProvider
|
||||
}
|
||||
|
||||
String _$selectedContainerTabStatesWithContainerHash() =>
|
||||
r'e2dc85fc72ee29736d8fdaabb1abadd23cc4bac0';
|
||||
r'187e408857ddc5fe08f8e5344beb510c3c39abe1';
|
||||
|
||||
@ProviderFor(quickTabSwitcherTabStates)
|
||||
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
||||
@@ -537,11 +537,11 @@ final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
||||
final class QuickTabSwitcherTabStatesProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
>
|
||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
||||
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||
QuickTabSwitcherTabStatesProvider._({
|
||||
required QuickTabSwitcherTabStatesFamily super.from,
|
||||
required QuickTabSwitcherMode super.argument,
|
||||
@@ -565,24 +565,24 @@ final class QuickTabSwitcherTabStatesProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
||||
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||
final argument = this.argument as QuickTabSwitcherMode;
|
||||
return quickTabSwitcherTabStates(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(
|
||||
EquatableValue<List<TabStateWirthContainer>> value,
|
||||
EquatableValue<List<TabStateWithContainer>> value,
|
||||
) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
@@ -601,12 +601,12 @@ final class QuickTabSwitcherTabStatesProvider
|
||||
}
|
||||
|
||||
String _$quickTabSwitcherTabStatesHash() =>
|
||||
r'df990fb96cca62a272572c925028725f81a0e96b';
|
||||
r'2627bcd25718472f0c8057e836d76bdaa7317c1c';
|
||||
|
||||
final class QuickTabSwitcherTabStatesFamily extends $Family
|
||||
with
|
||||
$FunctionalFamilyOverride<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
QuickTabSwitcherMode
|
||||
> {
|
||||
QuickTabSwitcherTabStatesFamily._()
|
||||
|
||||
+3
-2
@@ -587,8 +587,9 @@ class _TranslatePageTile extends ConsumerWidget {
|
||||
tabStateProvider(selectedTabId).select((s) => s?.translationState),
|
||||
);
|
||||
final readerActive = ref.watch(
|
||||
tabStateProvider(selectedTabId)
|
||||
.select((s) => s?.readerableState.active ?? false),
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((s) => s?.readerableState.active ?? false),
|
||||
);
|
||||
|
||||
// Hide when reader mode is active (Fenix-aligned)
|
||||
|
||||
@@ -588,12 +588,14 @@ class TabMenu extends HookConsumerWidget {
|
||||
builder: (context, ref, child) {
|
||||
final engineState = ref.watch(translationEngineStateProvider);
|
||||
final translationState = ref.watch(
|
||||
tabStateProvider(selectedTabId)
|
||||
.select((s) => s?.translationState),
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((s) => s?.translationState),
|
||||
);
|
||||
final readerActive = ref.watch(
|
||||
tabStateProvider(selectedTabId)
|
||||
.select((s) => s?.readerableState.active ?? false),
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((s) => s?.readerableState.active ?? false),
|
||||
);
|
||||
|
||||
// Hide when reader mode is active (Fenix-aligned)
|
||||
@@ -620,9 +622,7 @@ class TabMenu extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
isTranslated ? 'Translated' : 'Translate Page',
|
||||
),
|
||||
child: Text(isTranslated ? 'Translated' : 'Translate Page'),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -132,8 +132,10 @@ extension HitResultX on HitResult {
|
||||
HitResult withCleanedLink(String cleanedUrl) {
|
||||
return switch (this) {
|
||||
UnknownHitResult() => UnknownHitResult(src: cleanedUrl),
|
||||
ImageSrcHitResult(:final src) =>
|
||||
ImageSrcHitResult(src: src, uri: cleanedUrl),
|
||||
ImageSrcHitResult(:final src) => ImageSrcHitResult(
|
||||
src: src,
|
||||
uri: cleanedUrl,
|
||||
),
|
||||
_ => this,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,6 +56,20 @@ class HistoryRepository extends _$HistoryRepository {
|
||||
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
|
||||
void build() {}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class HistoryRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$historyRepositoryHash() => r'f9ccc32275bb2f95edbe53b2245b4843e8d0c439';
|
||||
String _$historyRepositoryHash() => r'19f883fdb644e34bc89483b4a8ddb60f54c52114';
|
||||
|
||||
abstract class _$HistoryRepository extends $Notifier<void> {
|
||||
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();
|
||||
}
|
||||
+339
@@ -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';
|
||||
}
|
||||
+11
-1
@@ -21,7 +21,17 @@ import 'package:riverpod_annotation/riverpod_annotation.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 }
|
||||
|
||||
|
||||
@@ -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/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/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_modules/bookmark_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/domain/providers/selected_container.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/presentation/hooks/on_listenable_change_selector.dart';
|
||||
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
|
||||
@@ -135,6 +142,17 @@ class SearchScreen extends HookConsumerWidget {
|
||||
source: searchTextController,
|
||||
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 textFieldKey = useMemoized(() => GlobalKey());
|
||||
final preferredHeight = useState<double>(kToolbarHeight);
|
||||
@@ -484,86 +502,211 @@ class SearchScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: ClipboardFillLink(controller: searchTextController),
|
||||
),
|
||||
FullSearchTermSuggestions(
|
||||
searchTextController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
submitSearch: submitSearch,
|
||||
domain: isEditMode ? existingTabState.url.host : null,
|
||||
),
|
||||
TabSearch(searchTextListenable: sampledSearchText),
|
||||
BookmarkSearch(
|
||||
searchTextListenable: sampledSearchText,
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
if (showNoInputSections) ...[
|
||||
HistoryHighlightsSection(
|
||||
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);
|
||||
}
|
||||
},
|
||||
),
|
||||
RecentFeedArticlesSection(
|
||||
onArticleSelected: (article) {
|
||||
FeedArticleRoute(articleId: article.id).go(context);
|
||||
},
|
||||
),
|
||||
RecentTabsSection(
|
||||
onTabSelected: (tabId) async {
|
||||
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,
|
||||
),
|
||||
.selectTab(tabId);
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
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) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
if (dialogResult == true) {
|
||||
await ref
|
||||
.read(startProxyControllerProvider.notifier)
|
||||
.startProxy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 &&
|
||||
result != SetContainerResult.failed) {
|
||||
const TabViewRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
if (!showNoInputSections) ...[
|
||||
FullSearchTermSuggestions(
|
||||
searchTextController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
submitSearch: submitSearch,
|
||||
domain: isEditMode ? existingTabState.url.host : null,
|
||||
),
|
||||
TabSearch(searchTextListenable: sampledSearchText),
|
||||
BookmarkSearch(
|
||||
searchTextListenable: sampledSearchText,
|
||||
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();
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.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);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
+60
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
@@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+109
@@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
@@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+68
@@ -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!,
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+6
@@ -42,6 +42,8 @@ import mozilla.components.support.webextensions.WebExtensionSupport
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val HISTORY_METADATA_MAX_AGE_IN_MS = 14L * 24 * 60 * 60 * 1000 // 14 days
|
||||
|
||||
object GlobalComponents {
|
||||
private var _components: Components? = null
|
||||
private var currentMode: ComponentsMode? = null
|
||||
@@ -203,6 +205,10 @@ object GlobalComponents {
|
||||
try {
|
||||
GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage)
|
||||
|
||||
newComponents.core.historyMetadataService.cleanup(
|
||||
System.currentTimeMillis() - HISTORY_METADATA_MAX_AGE_IN_MS,
|
||||
)
|
||||
|
||||
GlobalAddonDependencyProvider.initialize(
|
||||
newComponents.core.addonManager,
|
||||
newComponents.core.addonUpdater,
|
||||
|
||||
+30
@@ -2,6 +2,8 @@ package eu.weblibre.flutter_mozilla_components.api
|
||||
|
||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||
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.VisitType
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -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.ext.getPreferenceKey
|
||||
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.pigeons.BrowserExtensionEvents
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
|
||||
@@ -193,10 +195,15 @@ class Core(
|
||||
FileUploadsDirCleaner { context.cacheDir }
|
||||
}
|
||||
|
||||
val historyMetadataService by lazy {
|
||||
HistoryMetadataService(storage = historyStorage)
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
val store by lazy {
|
||||
BrowserStore(
|
||||
middleware = listOf(
|
||||
HistoryMetadataMiddleware(historyMetadataService),
|
||||
FlutterEventMiddleware(flutterEvents),
|
||||
DownloadMiddleware(context, DownloadService::class.java, { false }),
|
||||
ThumbnailsMiddleware(thumbnailStorage),
|
||||
|
||||
+114
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
-93
@@ -1669,6 +1669,77 @@ data class VisitInfo (
|
||||
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. */
|
||||
data class HistoryItem (
|
||||
val url: String,
|
||||
@@ -3985,235 +4056,245 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
185.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
HistoryItem.fromList(it)
|
||||
HistoryHighlightWeights.fromList(it)
|
||||
}
|
||||
}
|
||||
186.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
HistoryState.fromList(it)
|
||||
HistoryHighlight.fromList(it)
|
||||
}
|
||||
}
|
||||
187.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ReaderableState.fromList(it)
|
||||
HistoryItem.fromList(it)
|
||||
}
|
||||
}
|
||||
188.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SecurityInfoState.fromList(it)
|
||||
HistoryState.fromList(it)
|
||||
}
|
||||
}
|
||||
189.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TabContentState.fromList(it)
|
||||
ReaderableState.fromList(it)
|
||||
}
|
||||
}
|
||||
190.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
FindResultState.fromList(it)
|
||||
SecurityInfoState.fromList(it)
|
||||
}
|
||||
}
|
||||
191.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
CustomSelectionAction.fromList(it)
|
||||
TabContentState.fromList(it)
|
||||
}
|
||||
}
|
||||
192.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
WebExtensionData.fromList(it)
|
||||
FindResultState.fromList(it)
|
||||
}
|
||||
}
|
||||
193.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoSuggestion.fromList(it)
|
||||
CustomSelectionAction.fromList(it)
|
||||
}
|
||||
}
|
||||
194.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TabContent.fromList(it)
|
||||
WebExtensionData.fromList(it)
|
||||
}
|
||||
}
|
||||
195.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ContentBlocking.fromList(it)
|
||||
GeckoSuggestion.fromList(it)
|
||||
}
|
||||
}
|
||||
196.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
DohSettings.fromList(it)
|
||||
TabContent.fromList(it)
|
||||
}
|
||||
}
|
||||
197.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoEngineSettings.fromList(it)
|
||||
ContentBlocking.fromList(it)
|
||||
}
|
||||
}
|
||||
198.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
AutocompleteResult.fromList(it)
|
||||
DohSettings.fromList(it)
|
||||
}
|
||||
}
|
||||
199.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
UnknownHitResult.fromList(it)
|
||||
GeckoEngineSettings.fromList(it)
|
||||
}
|
||||
}
|
||||
200.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ImageHitResult.fromList(it)
|
||||
AutocompleteResult.fromList(it)
|
||||
}
|
||||
}
|
||||
201.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
VideoHitResult.fromList(it)
|
||||
UnknownHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
202.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
AudioHitResult.fromList(it)
|
||||
ImageHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
203.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ImageSrcHitResult.fromList(it)
|
||||
VideoHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
204.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
PhoneHitResult.fromList(it)
|
||||
AudioHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
205.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
EmailHitResult.fromList(it)
|
||||
ImageSrcHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
206.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeoHitResult.fromList(it)
|
||||
PhoneHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
207.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
DownloadState.fromList(it)
|
||||
EmailHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
208.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ShareInternetResourceState.fromList(it)
|
||||
GeoHitResult.fromList(it)
|
||||
}
|
||||
}
|
||||
209.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
AddonCollection.fromList(it)
|
||||
DownloadState.fromList(it)
|
||||
}
|
||||
}
|
||||
210.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SyncEngineStatus.fromList(it)
|
||||
ShareInternetResourceState.fromList(it)
|
||||
}
|
||||
}
|
||||
211.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SyncAccountInfo.fromList(it)
|
||||
AddonCollection.fromList(it)
|
||||
}
|
||||
}
|
||||
212.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SyncDevice.fromList(it)
|
||||
SyncEngineStatus.fromList(it)
|
||||
}
|
||||
}
|
||||
213.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SyncIncomingTab.fromList(it)
|
||||
SyncAccountInfo.fromList(it)
|
||||
}
|
||||
}
|
||||
214.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SyncRemoteTab.fromList(it)
|
||||
SyncDevice.fromList(it)
|
||||
}
|
||||
}
|
||||
215.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SyncDeviceTabs.fromList(it)
|
||||
SyncIncomingTab.fromList(it)
|
||||
}
|
||||
}
|
||||
216.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoPref.fromList(it)
|
||||
SyncRemoteTab.fromList(it)
|
||||
}
|
||||
}
|
||||
217.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
MlProgressData.fromList(it)
|
||||
SyncDeviceTabs.fromList(it)
|
||||
}
|
||||
}
|
||||
218.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ContainerSiteAssignment.fromList(it)
|
||||
GeckoPref.fromList(it)
|
||||
}
|
||||
}
|
||||
219.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoHeader.fromList(it)
|
||||
MlProgressData.fromList(it)
|
||||
}
|
||||
}
|
||||
220.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoFetchRequest.fromList(it)
|
||||
ContainerSiteAssignment.fromList(it)
|
||||
}
|
||||
}
|
||||
221.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoFetchResponse.fromList(it)
|
||||
GeckoHeader.fromList(it)
|
||||
}
|
||||
}
|
||||
222.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
BookmarkNode.fromList(it)
|
||||
GeckoFetchRequest.fromList(it)
|
||||
}
|
||||
}
|
||||
223.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
BookmarkInfo.fromList(it)
|
||||
GeckoFetchResponse.fromList(it)
|
||||
}
|
||||
}
|
||||
224.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SitePermissions.fromList(it)
|
||||
BookmarkNode.fromList(it)
|
||||
}
|
||||
}
|
||||
225.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TrackingProtectionException.fromList(it)
|
||||
BookmarkInfo.fromList(it)
|
||||
}
|
||||
}
|
||||
226.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
PwaIcon.fromList(it)
|
||||
SitePermissions.fromList(it)
|
||||
}
|
||||
}
|
||||
227.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ShareTargetFiles.fromList(it)
|
||||
TrackingProtectionException.fromList(it)
|
||||
}
|
||||
}
|
||||
228.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ShareTargetParams.fromList(it)
|
||||
PwaIcon.fromList(it)
|
||||
}
|
||||
}
|
||||
229.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ShareTarget.fromList(it)
|
||||
ShareTargetFiles.fromList(it)
|
||||
}
|
||||
}
|
||||
230.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ExternalApplicationResource.fromList(it)
|
||||
ShareTargetParams.fromList(it)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
PwaManifest.fromList(it)
|
||||
}
|
||||
@@ -4447,194 +4528,202 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
stream.write(184)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is HistoryItem -> {
|
||||
is HistoryHighlightWeights -> {
|
||||
stream.write(185)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is HistoryState -> {
|
||||
is HistoryHighlight -> {
|
||||
stream.write(186)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ReaderableState -> {
|
||||
is HistoryItem -> {
|
||||
stream.write(187)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SecurityInfoState -> {
|
||||
is HistoryState -> {
|
||||
stream.write(188)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TabContentState -> {
|
||||
is ReaderableState -> {
|
||||
stream.write(189)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is FindResultState -> {
|
||||
is SecurityInfoState -> {
|
||||
stream.write(190)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is CustomSelectionAction -> {
|
||||
is TabContentState -> {
|
||||
stream.write(191)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is WebExtensionData -> {
|
||||
is FindResultState -> {
|
||||
stream.write(192)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoSuggestion -> {
|
||||
is CustomSelectionAction -> {
|
||||
stream.write(193)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TabContent -> {
|
||||
is WebExtensionData -> {
|
||||
stream.write(194)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ContentBlocking -> {
|
||||
is GeckoSuggestion -> {
|
||||
stream.write(195)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is DohSettings -> {
|
||||
is TabContent -> {
|
||||
stream.write(196)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoEngineSettings -> {
|
||||
is ContentBlocking -> {
|
||||
stream.write(197)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is AutocompleteResult -> {
|
||||
is DohSettings -> {
|
||||
stream.write(198)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is UnknownHitResult -> {
|
||||
is GeckoEngineSettings -> {
|
||||
stream.write(199)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ImageHitResult -> {
|
||||
is AutocompleteResult -> {
|
||||
stream.write(200)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is VideoHitResult -> {
|
||||
is UnknownHitResult -> {
|
||||
stream.write(201)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is AudioHitResult -> {
|
||||
is ImageHitResult -> {
|
||||
stream.write(202)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ImageSrcHitResult -> {
|
||||
is VideoHitResult -> {
|
||||
stream.write(203)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is PhoneHitResult -> {
|
||||
is AudioHitResult -> {
|
||||
stream.write(204)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is EmailHitResult -> {
|
||||
is ImageSrcHitResult -> {
|
||||
stream.write(205)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeoHitResult -> {
|
||||
is PhoneHitResult -> {
|
||||
stream.write(206)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is DownloadState -> {
|
||||
is EmailHitResult -> {
|
||||
stream.write(207)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ShareInternetResourceState -> {
|
||||
is GeoHitResult -> {
|
||||
stream.write(208)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is AddonCollection -> {
|
||||
is DownloadState -> {
|
||||
stream.write(209)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SyncEngineStatus -> {
|
||||
is ShareInternetResourceState -> {
|
||||
stream.write(210)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SyncAccountInfo -> {
|
||||
is AddonCollection -> {
|
||||
stream.write(211)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SyncDevice -> {
|
||||
is SyncEngineStatus -> {
|
||||
stream.write(212)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SyncIncomingTab -> {
|
||||
is SyncAccountInfo -> {
|
||||
stream.write(213)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SyncRemoteTab -> {
|
||||
is SyncDevice -> {
|
||||
stream.write(214)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SyncDeviceTabs -> {
|
||||
is SyncIncomingTab -> {
|
||||
stream.write(215)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoPref -> {
|
||||
is SyncRemoteTab -> {
|
||||
stream.write(216)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is MlProgressData -> {
|
||||
is SyncDeviceTabs -> {
|
||||
stream.write(217)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ContainerSiteAssignment -> {
|
||||
is GeckoPref -> {
|
||||
stream.write(218)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoHeader -> {
|
||||
is MlProgressData -> {
|
||||
stream.write(219)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoFetchRequest -> {
|
||||
is ContainerSiteAssignment -> {
|
||||
stream.write(220)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoFetchResponse -> {
|
||||
is GeckoHeader -> {
|
||||
stream.write(221)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is BookmarkNode -> {
|
||||
is GeckoFetchRequest -> {
|
||||
stream.write(222)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is BookmarkInfo -> {
|
||||
is GeckoFetchResponse -> {
|
||||
stream.write(223)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SitePermissions -> {
|
||||
is BookmarkNode -> {
|
||||
stream.write(224)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TrackingProtectionException -> {
|
||||
is BookmarkInfo -> {
|
||||
stream.write(225)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is PwaIcon -> {
|
||||
is SitePermissions -> {
|
||||
stream.write(226)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ShareTargetFiles -> {
|
||||
is TrackingProtectionException -> {
|
||||
stream.write(227)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ShareTargetParams -> {
|
||||
is PwaIcon -> {
|
||||
stream.write(228)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ShareTarget -> {
|
||||
is ShareTargetFiles -> {
|
||||
stream.write(229)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ExternalApplicationResource -> {
|
||||
is ShareTargetParams -> {
|
||||
stream.write(230)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is PwaManifest -> {
|
||||
is ShareTarget -> {
|
||||
stream.write(231)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -7531,6 +7620,7 @@ interface GeckoHistoryApi {
|
||||
fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit)
|
||||
fun deleteDownload(id: String, 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 {
|
||||
/** The codec used by GeckoHistoryApi. */
|
||||
@@ -7644,6 +7734,27 @@ interface GeckoHistoryApi {
|
||||
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,
|
||||
GeckoTrackingProtectionApi,
|
||||
GeoHitResult,
|
||||
HistoryHighlight,
|
||||
HistoryHighlightWeights,
|
||||
HistoryMetadataKey,
|
||||
HitResult,
|
||||
HttpsOnlyMode,
|
||||
|
||||
@@ -48,8 +48,7 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
final _tabAddedSubject = PublishSubject<String>();
|
||||
final _mlProgressSubject = PublishSubject<MlProgressData>();
|
||||
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
|
||||
final _translationEngineSubject =
|
||||
BehaviorSubject<TranslationEngineEvent>();
|
||||
final _translationEngineSubject = BehaviorSubject<TranslationEngineEvent>();
|
||||
final _tabTranslationSubject = ReplaySubject<TabTranslationEvent>();
|
||||
|
||||
// Event streams
|
||||
|
||||
@@ -60,4 +60,11 @@ class GeckoHistoryService {
|
||||
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 {
|
||||
final String url;
|
||||
final String title;
|
||||
@@ -1639,10 +1662,7 @@ abstract class GeckoStateEvents {
|
||||
int sequence,
|
||||
TranslationEngineStateData state,
|
||||
);
|
||||
void onTabTranslationStateChange(
|
||||
int sequence,
|
||||
TabTranslationStateData state,
|
||||
);
|
||||
void onTabTranslationStateChange(int sequence, TabTranslationStateData state);
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
@@ -1797,6 +1817,12 @@ abstract class GeckoHistoryApi {
|
||||
|
||||
@async
|
||||
void deleteVisitsBetween(int startMillis, int endMillis);
|
||||
|
||||
@async
|
||||
List<HistoryHighlight> getHistoryHighlights(
|
||||
HistoryHighlightWeights weights,
|
||||
int limit,
|
||||
);
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
|
||||
@@ -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) {
|
||||
return <Object?>[];
|
||||
}
|
||||
@@ -24,37 +28,48 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
|
||||
}
|
||||
return <Object?>[error.code, error.message, error.details];
|
||||
}
|
||||
|
||||
bool _deepEquals(Object? a, Object? b) {
|
||||
if (a is List && b is List) {
|
||||
return a.length == b.length &&
|
||||
a.indexed
|
||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||
a.indexed.every(
|
||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||
);
|
||||
}
|
||||
if (a is Map && b is Map) {
|
||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]));
|
||||
return a.length == b.length &&
|
||||
a.entries.every(
|
||||
(MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]),
|
||||
);
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
|
||||
|
||||
/// Transport types for Tor connections
|
||||
enum TransportType {
|
||||
/// Direct Tor connection (no bridges)
|
||||
none,
|
||||
|
||||
/// obfs4 pluggable transport
|
||||
obfs4,
|
||||
|
||||
/// Snowflake pluggable transport (default broker)
|
||||
snowflake,
|
||||
|
||||
/// Snowflake via AMP cache
|
||||
snowflakeAmp,
|
||||
|
||||
/// Meek pluggable transport
|
||||
meek,
|
||||
|
||||
/// Meek via Azure CDN
|
||||
meekAzure,
|
||||
|
||||
/// WebTunnel pluggable transport
|
||||
webtunnel,
|
||||
|
||||
/// Custom bridge lines (passthrough)
|
||||
custom,
|
||||
}
|
||||
@@ -95,7 +110,8 @@ class TorConfiguration {
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList(); }
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static TorConfiguration decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@@ -122,8 +138,7 @@ class TorConfiguration {
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hashAll(_toList())
|
||||
;
|
||||
int get hashCode => Object.hashAll(_toList());
|
||||
}
|
||||
|
||||
/// Current Tor status
|
||||
@@ -162,7 +177,8 @@ class TorStatus {
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList(); }
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static TorStatus decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@@ -189,8 +205,7 @@ class TorStatus {
|
||||
|
||||
@override
|
||||
// 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
|
||||
@@ -211,15 +226,12 @@ class TorLogMessage {
|
||||
int timestamp;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
severity,
|
||||
message,
|
||||
timestamp,
|
||||
];
|
||||
return <Object?>[severity, message, timestamp];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList(); }
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static TorLogMessage decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@@ -244,11 +256,9 @@ class TorLogMessage {
|
||||
|
||||
@override
|
||||
// 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 {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@@ -256,16 +266,16 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is TransportType) {
|
||||
} else if (value is TransportType) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is TorConfiguration) {
|
||||
} else if (value is TorConfiguration) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is TorStatus) {
|
||||
} else if (value is TorStatus) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is TorLogMessage) {
|
||||
} else if (value is TorLogMessage) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
@@ -276,14 +286,14 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
case 129:
|
||||
final value = readValue(buffer) as int?;
|
||||
return value == null ? null : TransportType.values[value];
|
||||
case 130:
|
||||
case 130:
|
||||
return TorConfiguration.decode(readValue(buffer)!);
|
||||
case 131:
|
||||
case 131:
|
||||
return TorStatus.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
case 132:
|
||||
return TorLogMessage.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@@ -297,8 +307,10 @@ class TorApi {
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
@@ -308,13 +320,16 @@ class TorApi {
|
||||
/// Start Tor with the given configuration
|
||||
/// Returns a Future to avoid blocking the main thread
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
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?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
@@ -336,7 +351,8 @@ class TorApi {
|
||||
|
||||
/// Stop Tor
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
@@ -359,7 +375,8 @@ class TorApi {
|
||||
|
||||
/// Get current status
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
@@ -387,7 +404,8 @@ class TorApi {
|
||||
|
||||
/// Request a new Tor identity (new circuit)
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
@@ -419,54 +437,76 @@ abstract class TorLogApi {
|
||||
/// Called when status changes
|
||||
void onStatusChanged(TorStatus status);
|
||||
|
||||
static void setUp(TorLogApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
static void setUp(
|
||||
TorLogApi? api, {
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.');
|
||||
assert(
|
||||
message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.',
|
||||
);
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final TorLogMessage? arg_log = (args[0] as TorLogMessage?);
|
||||
assert(arg_log != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.');
|
||||
assert(
|
||||
arg_log != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.',
|
||||
);
|
||||
try {
|
||||
api.onLogMessage(arg_log!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.');
|
||||
assert(
|
||||
message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.',
|
||||
);
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final TorStatus? arg_status = (args[0] as TorStatus?);
|
||||
assert(arg_status != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.');
|
||||
assert(
|
||||
arg_status != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.',
|
||||
);
|
||||
try {
|
||||
api.onStatusChanged(arg_status!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -478,9 +518,13 @@ class IPtProxyController {
|
||||
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
IPtProxyController({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
@@ -488,13 +532,16 @@ class IPtProxyController {
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
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?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
@@ -515,13 +562,16 @@ class IPtProxyController {
|
||||
}
|
||||
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
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?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
|
||||
@@ -14,40 +14,39 @@ PlatformException _createConnectionError(String channelName) {
|
||||
message: 'Unable to establish connection on channel: "$channelName".',
|
||||
);
|
||||
}
|
||||
|
||||
bool _deepEquals(Object? a, Object? b) {
|
||||
if (a is List && b is List) {
|
||||
return a.length == b.length &&
|
||||
a.indexed
|
||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||
a.indexed.every(
|
||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||
);
|
||||
}
|
||||
if (a is Map && b is Map) {
|
||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]));
|
||||
return a.length == b.length &&
|
||||
a.entries.every(
|
||||
(MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]),
|
||||
);
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
|
||||
|
||||
class LocalizedResult {
|
||||
LocalizedResult({
|
||||
required this.languageName,
|
||||
this.countryName,
|
||||
});
|
||||
LocalizedResult({required this.languageName, this.countryName});
|
||||
|
||||
String languageName;
|
||||
|
||||
String? countryName;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
languageName,
|
||||
countryName,
|
||||
];
|
||||
return <Object?>[languageName, countryName];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList(); }
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static LocalizedResult decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@@ -71,11 +70,9 @@ class LocalizedResult {
|
||||
|
||||
@override
|
||||
// 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 {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@@ -83,7 +80,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is LocalizedResult) {
|
||||
} else if (value is LocalizedResult) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
@@ -94,7 +91,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
case 129:
|
||||
return LocalizedResult.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@@ -106,23 +103,33 @@ class LocaleResolver {
|
||||
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
LocaleResolver({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
LocaleResolver({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
Future<LocalizedResult> resolve(String languageTag, String targetLangouageTag) async {
|
||||
final pigeonVar_channelName = 'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
||||
Future<LocalizedResult> resolve(
|
||||
String languageTag,
|
||||
String targetLangouageTag,
|
||||
) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
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?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
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/services.dart';
|
||||
|
||||
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||
List<Object?> wrapResponse({
|
||||
Object? result,
|
||||
PlatformException? error,
|
||||
bool empty = false,
|
||||
}) {
|
||||
if (empty) {
|
||||
return <Object?>[];
|
||||
}
|
||||
@@ -17,21 +21,25 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
|
||||
}
|
||||
return <Object?>[error.code, error.message, error.details];
|
||||
}
|
||||
|
||||
bool _deepEquals(Object? a, Object? b) {
|
||||
if (a is List && b is List) {
|
||||
return a.length == b.length &&
|
||||
a.indexed
|
||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||
a.indexed.every(
|
||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||
);
|
||||
}
|
||||
if (a is Map && b is Map) {
|
||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]));
|
||||
return a.length == b.length &&
|
||||
a.entries.every(
|
||||
(MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]),
|
||||
);
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
|
||||
|
||||
class Intent {
|
||||
Intent({
|
||||
this.fromPackageName,
|
||||
@@ -66,7 +74,8 @@ class Intent {
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList(); }
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static Intent decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@@ -94,11 +103,9 @@ class Intent {
|
||||
|
||||
@override
|
||||
// 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 {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@@ -106,7 +113,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is Intent) {
|
||||
} else if (value is Intent) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
@@ -117,7 +124,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
case 129:
|
||||
return Intent.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@@ -130,32 +137,48 @@ abstract class IntentEvents {
|
||||
|
||||
void onIntentReceived(int sequence, Intent intent);
|
||||
|
||||
static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
static void setUp(
|
||||
IntentEvents? api, {
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.');
|
||||
assert(
|
||||
message != null,
|
||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.',
|
||||
);
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final int? arg_sequence = (args[0] as int?);
|
||||
assert(arg_sequence != null,
|
||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.');
|
||||
assert(
|
||||
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?);
|
||||
assert(arg_intent != null,
|
||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.');
|
||||
assert(
|
||||
arg_intent != null,
|
||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.',
|
||||
);
|
||||
try {
|
||||
api.onIntentReceived(arg_sequence!, arg_intent!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
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) {
|
||||
return <Object?>[];
|
||||
}
|
||||
@@ -25,7 +29,6 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
|
||||
return <Object?>[error.code, error.message, error.details];
|
||||
}
|
||||
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@@ -52,9 +55,13 @@ class SpeechToTextApi {
|
||||
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
SpeechToTextApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
SpeechToTextApi({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
@@ -69,13 +76,16 @@ class SpeechToTextApi {
|
||||
/// The [locale] parameter specifies the language locale for recognition
|
||||
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
||||
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?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
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?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
@@ -106,29 +116,43 @@ abstract class SpeechToTextEvents {
|
||||
/// recognition failed or was cancelled.
|
||||
void onTextReceived(String text);
|
||||
|
||||
static void setUp(SpeechToTextEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
static void setUp(
|
||||
SpeechToTextEvents? api, {
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.');
|
||||
assert(
|
||||
message != null,
|
||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.',
|
||||
);
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_text = (args[0] as String?);
|
||||
assert(arg_text != null,
|
||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.');
|
||||
assert(
|
||||
arg_text != null,
|
||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.',
|
||||
);
|
||||
try {
|
||||
api.onTextReceived(arg_text!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user