added many suggestion modules
This commit is contained in:
@@ -66,8 +66,7 @@ class TranslationState with FastEquatable {
|
|||||||
displayError: data.displayError,
|
displayError: data.displayError,
|
||||||
);
|
);
|
||||||
|
|
||||||
bool get hasError =>
|
bool get hasError => translationErrorName != null && (displayError ?? true);
|
||||||
translationErrorName != null && (displayError ?? true);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ class BookmarkSearchResults extends _$BookmarkSearchResults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final results = await _service.searchBookmarks(query, limit: limit);
|
final results = await _service.searchBookmarks(query, limit: limit);
|
||||||
|
if (!ref.mounted) return;
|
||||||
state = results
|
state = results
|
||||||
.map(BookmarkItem.parseRecursive)
|
.map(BookmarkItem.parseRecursive)
|
||||||
.whereType<BookmarkEntry>()
|
.whereType<BookmarkEntry>()
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ final class BookmarkSearchResultsProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$bookmarkSearchResultsHash() =>
|
String _$bookmarkSearchResultsHash() =>
|
||||||
r'f1413466db26c1e3407501453b8cec97504a771d';
|
r'44af94b2234c817b08b0bde2dc68f9ab8d2d0a9a';
|
||||||
|
|
||||||
abstract class _$BookmarkSearchResults extends $Notifier<List<BookmarkEntry>> {
|
abstract class _$BookmarkSearchResults extends $Notifier<List<BookmarkEntry>> {
|
||||||
List<BookmarkEntry> build();
|
List<BookmarkEntry> build();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
|||||||
|
|
||||||
part 'providers.g.dart';
|
part 'providers.g.dart';
|
||||||
|
|
||||||
typedef TabStateWirthContainer = (TabState, ContainerData?);
|
typedef TabStateWithContainer = (TabState, ContainerData?);
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
bool canManualTabReorder(Ref ref) {
|
bool canManualTabReorder(Ref ref) {
|
||||||
@@ -175,7 +175,7 @@ EquatableValue<Map<String, TabState>> containerTabStates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
|
EquatableValue<List<TabStateWithContainer>> fifoTabStates(Ref ref) {
|
||||||
final containerData = ref
|
final containerData = ref
|
||||||
.watch(watchContainersWithCountProvider.select((value) => value.value))
|
.watch(watchContainersWithCountProvider.select((value) => value.value))
|
||||||
.mapNotNull(
|
.mapNotNull(
|
||||||
@@ -202,7 +202,7 @@ EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
EquatableValue<List<TabStateWirthContainer>>
|
EquatableValue<List<TabStateWithContainer>>
|
||||||
selectedContainerTabStatesWithContainer(Ref ref) {
|
selectedContainerTabStatesWithContainer(Ref ref) {
|
||||||
final filter = ref.watch(
|
final filter = ref.watch(
|
||||||
selectedContainerProvider.select(
|
selectedContainerProvider.select(
|
||||||
@@ -256,7 +256,7 @@ selectedContainerTabStatesWithContainer(Ref ref) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
EquatableValue<List<TabStateWirthContainer>> quickTabSwitcherTabStates(
|
EquatableValue<List<TabStateWithContainer>> quickTabSwitcherTabStates(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
QuickTabSwitcherMode mode,
|
QuickTabSwitcherMode mode,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -430,11 +430,11 @@ final fifoTabStatesProvider = FifoTabStatesProvider._();
|
|||||||
final class FifoTabStatesProvider
|
final class FifoTabStatesProvider
|
||||||
extends
|
extends
|
||||||
$FunctionalProvider<
|
$FunctionalProvider<
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
EquatableValue<List<TabStateWirthContainer>>
|
EquatableValue<List<TabStateWithContainer>>
|
||||||
>
|
>
|
||||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||||
FifoTabStatesProvider._()
|
FifoTabStatesProvider._()
|
||||||
: super(
|
: super(
|
||||||
from: null,
|
from: null,
|
||||||
@@ -451,30 +451,30 @@ final class FifoTabStatesProvider
|
|||||||
|
|
||||||
@$internal
|
@$internal
|
||||||
@override
|
@override
|
||||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||||
$ProviderPointer pointer,
|
$ProviderPointer pointer,
|
||||||
) => $ProviderElement(pointer);
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||||
return fifoTabStates(ref);
|
return fifoTabStates(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
/// {@macro riverpod.override_with_value}
|
||||||
Override overrideWithValue(
|
Override overrideWithValue(
|
||||||
EquatableValue<List<TabStateWirthContainer>> value,
|
EquatableValue<List<TabStateWithContainer>> value,
|
||||||
) {
|
) {
|
||||||
return $ProviderOverride(
|
return $ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
providerOverride:
|
providerOverride:
|
||||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$fifoTabStatesHash() => r'03298f30559d55e986d97a17d517b9a9abb89be4';
|
String _$fifoTabStatesHash() => r'f8dc69382d307bc83193a3b839a16313e9c1ab69';
|
||||||
|
|
||||||
@ProviderFor(selectedContainerTabStatesWithContainer)
|
@ProviderFor(selectedContainerTabStatesWithContainer)
|
||||||
final selectedContainerTabStatesWithContainerProvider =
|
final selectedContainerTabStatesWithContainerProvider =
|
||||||
@@ -483,11 +483,11 @@ final selectedContainerTabStatesWithContainerProvider =
|
|||||||
final class SelectedContainerTabStatesWithContainerProvider
|
final class SelectedContainerTabStatesWithContainerProvider
|
||||||
extends
|
extends
|
||||||
$FunctionalProvider<
|
$FunctionalProvider<
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
EquatableValue<List<TabStateWirthContainer>>
|
EquatableValue<List<TabStateWithContainer>>
|
||||||
>
|
>
|
||||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||||
SelectedContainerTabStatesWithContainerProvider._()
|
SelectedContainerTabStatesWithContainerProvider._()
|
||||||
: super(
|
: super(
|
||||||
from: null,
|
from: null,
|
||||||
@@ -505,23 +505,23 @@ final class SelectedContainerTabStatesWithContainerProvider
|
|||||||
|
|
||||||
@$internal
|
@$internal
|
||||||
@override
|
@override
|
||||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||||
$ProviderPointer pointer,
|
$ProviderPointer pointer,
|
||||||
) => $ProviderElement(pointer);
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||||
return selectedContainerTabStatesWithContainer(ref);
|
return selectedContainerTabStatesWithContainer(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
/// {@macro riverpod.override_with_value}
|
||||||
Override overrideWithValue(
|
Override overrideWithValue(
|
||||||
EquatableValue<List<TabStateWirthContainer>> value,
|
EquatableValue<List<TabStateWithContainer>> value,
|
||||||
) {
|
) {
|
||||||
return $ProviderOverride(
|
return $ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
providerOverride:
|
providerOverride:
|
||||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -529,7 +529,7 @@ final class SelectedContainerTabStatesWithContainerProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$selectedContainerTabStatesWithContainerHash() =>
|
String _$selectedContainerTabStatesWithContainerHash() =>
|
||||||
r'e2dc85fc72ee29736d8fdaabb1abadd23cc4bac0';
|
r'187e408857ddc5fe08f8e5344beb510c3c39abe1';
|
||||||
|
|
||||||
@ProviderFor(quickTabSwitcherTabStates)
|
@ProviderFor(quickTabSwitcherTabStates)
|
||||||
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
||||||
@@ -537,11 +537,11 @@ final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
|||||||
final class QuickTabSwitcherTabStatesProvider
|
final class QuickTabSwitcherTabStatesProvider
|
||||||
extends
|
extends
|
||||||
$FunctionalProvider<
|
$FunctionalProvider<
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
EquatableValue<List<TabStateWirthContainer>>
|
EquatableValue<List<TabStateWithContainer>>
|
||||||
>
|
>
|
||||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||||
QuickTabSwitcherTabStatesProvider._({
|
QuickTabSwitcherTabStatesProvider._({
|
||||||
required QuickTabSwitcherTabStatesFamily super.from,
|
required QuickTabSwitcherTabStatesFamily super.from,
|
||||||
required QuickTabSwitcherMode super.argument,
|
required QuickTabSwitcherMode super.argument,
|
||||||
@@ -565,24 +565,24 @@ final class QuickTabSwitcherTabStatesProvider
|
|||||||
|
|
||||||
@$internal
|
@$internal
|
||||||
@override
|
@override
|
||||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||||
$ProviderPointer pointer,
|
$ProviderPointer pointer,
|
||||||
) => $ProviderElement(pointer);
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||||
final argument = this.argument as QuickTabSwitcherMode;
|
final argument = this.argument as QuickTabSwitcherMode;
|
||||||
return quickTabSwitcherTabStates(ref, argument);
|
return quickTabSwitcherTabStates(ref, argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
/// {@macro riverpod.override_with_value}
|
||||||
Override overrideWithValue(
|
Override overrideWithValue(
|
||||||
EquatableValue<List<TabStateWirthContainer>> value,
|
EquatableValue<List<TabStateWithContainer>> value,
|
||||||
) {
|
) {
|
||||||
return $ProviderOverride(
|
return $ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
providerOverride:
|
providerOverride:
|
||||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -601,12 +601,12 @@ final class QuickTabSwitcherTabStatesProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$quickTabSwitcherTabStatesHash() =>
|
String _$quickTabSwitcherTabStatesHash() =>
|
||||||
r'df990fb96cca62a272572c925028725f81a0e96b';
|
r'2627bcd25718472f0c8057e836d76bdaa7317c1c';
|
||||||
|
|
||||||
final class QuickTabSwitcherTabStatesFamily extends $Family
|
final class QuickTabSwitcherTabStatesFamily extends $Family
|
||||||
with
|
with
|
||||||
$FunctionalFamilyOverride<
|
$FunctionalFamilyOverride<
|
||||||
EquatableValue<List<TabStateWirthContainer>>,
|
EquatableValue<List<TabStateWithContainer>>,
|
||||||
QuickTabSwitcherMode
|
QuickTabSwitcherMode
|
||||||
> {
|
> {
|
||||||
QuickTabSwitcherTabStatesFamily._()
|
QuickTabSwitcherTabStatesFamily._()
|
||||||
|
|||||||
+3
-2
@@ -587,8 +587,9 @@ class _TranslatePageTile extends ConsumerWidget {
|
|||||||
tabStateProvider(selectedTabId).select((s) => s?.translationState),
|
tabStateProvider(selectedTabId).select((s) => s?.translationState),
|
||||||
);
|
);
|
||||||
final readerActive = ref.watch(
|
final readerActive = ref.watch(
|
||||||
tabStateProvider(selectedTabId)
|
tabStateProvider(
|
||||||
.select((s) => s?.readerableState.active ?? false),
|
selectedTabId,
|
||||||
|
).select((s) => s?.readerableState.active ?? false),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hide when reader mode is active (Fenix-aligned)
|
// Hide when reader mode is active (Fenix-aligned)
|
||||||
|
|||||||
@@ -588,12 +588,14 @@ class TabMenu extends HookConsumerWidget {
|
|||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
final engineState = ref.watch(translationEngineStateProvider);
|
final engineState = ref.watch(translationEngineStateProvider);
|
||||||
final translationState = ref.watch(
|
final translationState = ref.watch(
|
||||||
tabStateProvider(selectedTabId)
|
tabStateProvider(
|
||||||
.select((s) => s?.translationState),
|
selectedTabId,
|
||||||
|
).select((s) => s?.translationState),
|
||||||
);
|
);
|
||||||
final readerActive = ref.watch(
|
final readerActive = ref.watch(
|
||||||
tabStateProvider(selectedTabId)
|
tabStateProvider(
|
||||||
.select((s) => s?.readerableState.active ?? false),
|
selectedTabId,
|
||||||
|
).select((s) => s?.readerableState.active ?? false),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hide when reader mode is active (Fenix-aligned)
|
// Hide when reader mode is active (Fenix-aligned)
|
||||||
@@ -620,9 +622,7 @@ class TabMenu extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(isTranslated ? 'Translated' : 'Translate Page'),
|
||||||
isTranslated ? 'Translated' : 'Translate Page',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -132,8 +132,10 @@ extension HitResultX on HitResult {
|
|||||||
HitResult withCleanedLink(String cleanedUrl) {
|
HitResult withCleanedLink(String cleanedUrl) {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
UnknownHitResult() => UnknownHitResult(src: cleanedUrl),
|
UnknownHitResult() => UnknownHitResult(src: cleanedUrl),
|
||||||
ImageSrcHitResult(:final src) =>
|
ImageSrcHitResult(:final src) => ImageSrcHitResult(
|
||||||
ImageSrcHitResult(src: src, uri: cleanedUrl),
|
src: src,
|
||||||
|
uri: cleanedUrl,
|
||||||
|
),
|
||||||
_ => this,
|
_ => this,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ class HistoryRepository extends _$HistoryRepository {
|
|||||||
return _service.deleteVisit(info);
|
return _service.deleteVisit(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<HistoryHighlight>> getHistoryHighlights({
|
||||||
|
double viewTimeWeight = 10.0,
|
||||||
|
double frequencyWeight = 4.0,
|
||||||
|
required int limit,
|
||||||
|
}) {
|
||||||
|
return _service.getHistoryHighlights(
|
||||||
|
weights: HistoryHighlightWeights(
|
||||||
|
viewTime: viewTimeWeight,
|
||||||
|
frequency: frequencyWeight,
|
||||||
|
),
|
||||||
|
limit: limit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void build() {}
|
void build() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class HistoryRepositoryProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$historyRepositoryHash() => r'f9ccc32275bb2f95edbe53b2245b4843e8d0c439';
|
String _$historyRepositoryHash() => r'19f883fdb644e34bc89483b4a8ddb60f54c52114';
|
||||||
|
|
||||||
abstract class _$HistoryRepository extends $Notifier<void> {
|
abstract class _$HistoryRepository extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2026 Fabian Freund.
|
||||||
|
*
|
||||||
|
* This file is part of WebLibre
|
||||||
|
* (see https://weblibre.eu).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
|
||||||
|
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||||
|
import 'package:weblibre/features/web_feed/domain/providers.dart';
|
||||||
|
|
||||||
|
part 'empty_state_content.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Future<List<HistoryHighlight>> searchEmptyHistoryHighlights(
|
||||||
|
Ref ref, {
|
||||||
|
int count = 25,
|
||||||
|
}) async {
|
||||||
|
final highlights = await ref
|
||||||
|
.read(historyRepositoryProvider.notifier)
|
||||||
|
.getHistoryHighlights(limit: count);
|
||||||
|
|
||||||
|
if (!ref.mounted) return [];
|
||||||
|
|
||||||
|
return highlights.where((h) => Uri.tryParse(h.url) != null).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Future<List<VisitInfo>> searchEmptyRecentHistory(
|
||||||
|
Ref ref, {
|
||||||
|
int count = 25,
|
||||||
|
}) async {
|
||||||
|
final visits = await ref
|
||||||
|
.read(historyRepositoryProvider.notifier)
|
||||||
|
.getVisitsPaginated(count: count);
|
||||||
|
|
||||||
|
if (!ref.mounted) return [];
|
||||||
|
|
||||||
|
return visits.where((v) => Uri.tryParse(v.url) != null).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
AsyncValue<List<FeedArticle>> searchEmptyRecentFeedArticles(
|
||||||
|
Ref ref, {
|
||||||
|
int count = 25,
|
||||||
|
}) {
|
||||||
|
final feeds = ref.watch(feedListProvider);
|
||||||
|
|
||||||
|
final hasFeeds = feeds.whenOrNull(data: (list) => list.isNotEmpty) ?? false;
|
||||||
|
if (!hasFeeds) {
|
||||||
|
return const AsyncValue.data([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref
|
||||||
|
.watch(feedArticleListProvider(null))
|
||||||
|
.whenData((articles) => articles.take(count).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
List<TabStateWithContainer> searchEmptyRecentTabs(Ref ref, {int count = 25}) {
|
||||||
|
final tabs = ref.watch(fifoTabStatesProvider).value;
|
||||||
|
|
||||||
|
return tabs.take(count).toList();
|
||||||
|
}
|
||||||
+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';
|
part 'search_modules_view.g.dart';
|
||||||
|
|
||||||
enum SearchModuleType { tabs, articles, bookmarks, history }
|
enum SearchModuleType {
|
||||||
|
tabs,
|
||||||
|
articles,
|
||||||
|
bookmarks,
|
||||||
|
history,
|
||||||
|
historyHighlights,
|
||||||
|
recentHistory,
|
||||||
|
recentArticles,
|
||||||
|
recentTabs,
|
||||||
|
containers,
|
||||||
|
}
|
||||||
|
|
||||||
enum SearchModuleDisplayState { preview, expanded, collapsed }
|
enum SearchModuleDisplayState { preview, expanded, collapsed }
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/containers_section.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/history_highlights_section.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_feed_articles_section.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_history_section.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_tabs_section.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart';
|
||||||
@@ -45,6 +50,8 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/isolatio
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart';
|
||||||
|
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||||
|
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
|
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
|
||||||
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
|
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
|
||||||
@@ -135,6 +142,17 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
source: searchTextController,
|
source: searchTextController,
|
||||||
sampleDuration: const Duration(milliseconds: 150),
|
sampleDuration: const Duration(milliseconds: 150),
|
||||||
);
|
);
|
||||||
|
final hasUserProvidedInput = useState(
|
||||||
|
initialSearchText?.isNotEmpty == true,
|
||||||
|
);
|
||||||
|
|
||||||
|
useOnListenableChange(searchTextController, () {
|
||||||
|
hasUserProvidedInput.value = searchTextController.text.isNotEmpty;
|
||||||
|
});
|
||||||
|
|
||||||
|
final showNoInputSections =
|
||||||
|
!hasUserProvidedInput.value && searchTextController.text.isEmpty;
|
||||||
|
|
||||||
final searchFocusNode = useFocusNode();
|
final searchFocusNode = useFocusNode();
|
||||||
final textFieldKey = useMemoized(() => GlobalKey());
|
final textFieldKey = useMemoized(() => GlobalKey());
|
||||||
final preferredHeight = useState<double>(kToolbarHeight);
|
final preferredHeight = useState<double>(kToolbarHeight);
|
||||||
@@ -484,86 +502,211 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: ClipboardFillLink(controller: searchTextController),
|
child: ClipboardFillLink(controller: searchTextController),
|
||||||
),
|
),
|
||||||
FullSearchTermSuggestions(
|
if (showNoInputSections) ...[
|
||||||
searchTextController: searchTextController,
|
HistoryHighlightsSection(
|
||||||
activeBang: activeBang,
|
onUriSelected: (uri) async {
|
||||||
submitSearch: submitSearch,
|
if (isEditMode) {
|
||||||
domain: isEditMode ? existingTabState.url.host : null,
|
await ref
|
||||||
),
|
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||||
TabSearch(searchTextListenable: sampledSearchText),
|
.loadUrl(url: uri);
|
||||||
BookmarkSearch(
|
} else {
|
||||||
searchTextListenable: sampledSearchText,
|
await ref
|
||||||
onUriSelected: (uri) async {
|
.read(tabRepositoryProvider.notifier)
|
||||||
if (isEditMode) {
|
.addTab(
|
||||||
await ref
|
url: uri,
|
||||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
tabMode: effectiveTabMode,
|
||||||
.loadUrl(url: uri);
|
parentId: (selectedTabType.value == TabType.child)
|
||||||
} else {
|
? ref.read(selectedTabProvider)
|
||||||
|
: null,
|
||||||
|
launchedFromIntent: launchedFromIntent,
|
||||||
|
selectTab: true,
|
||||||
|
containerSelection: selectedContainer == null
|
||||||
|
? const TabContainerSelection.unassigned()
|
||||||
|
: TabContainerSelection.specific(
|
||||||
|
selectedContainer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ref
|
||||||
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
|
.requestDismiss();
|
||||||
|
|
||||||
|
const BrowserRoute().go(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RecentFeedArticlesSection(
|
||||||
|
onArticleSelected: (article) {
|
||||||
|
FeedArticleRoute(articleId: article.id).go(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RecentTabsSection(
|
||||||
|
onTabSelected: (tabId) async {
|
||||||
await ref
|
await ref
|
||||||
.read(tabRepositoryProvider.notifier)
|
.read(tabRepositoryProvider.notifier)
|
||||||
.addTab(
|
.selectTab(tabId);
|
||||||
url: uri,
|
|
||||||
tabMode: effectiveTabMode,
|
if (context.mounted) {
|
||||||
parentId: (selectedTabType.value == TabType.child)
|
ref
|
||||||
? ref.read(selectedTabProvider)
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
: null,
|
.requestDismiss();
|
||||||
launchedFromIntent: launchedFromIntent,
|
|
||||||
selectTab: true,
|
const BrowserRoute().go(context);
|
||||||
containerSelection: selectedContainer == null
|
}
|
||||||
? const TabContainerSelection.unassigned()
|
},
|
||||||
: TabContainerSelection.specific(
|
),
|
||||||
selectedContainer,
|
RecentHistorySection(
|
||||||
),
|
onUriSelected: (uri) async {
|
||||||
|
if (isEditMode) {
|
||||||
|
await ref
|
||||||
|
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||||
|
.loadUrl(url: uri);
|
||||||
|
} else {
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(
|
||||||
|
url: uri,
|
||||||
|
tabMode: effectiveTabMode,
|
||||||
|
parentId: (selectedTabType.value == TabType.child)
|
||||||
|
? ref.read(selectedTabProvider)
|
||||||
|
: null,
|
||||||
|
launchedFromIntent: launchedFromIntent,
|
||||||
|
selectTab: true,
|
||||||
|
containerSelection: selectedContainer == null
|
||||||
|
? const TabContainerSelection.unassigned()
|
||||||
|
: TabContainerSelection.specific(
|
||||||
|
selectedContainer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ref
|
||||||
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
|
.requestDismiss();
|
||||||
|
|
||||||
|
const BrowserRoute().go(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ContainersSection(
|
||||||
|
onContainerSelected: (container) async {
|
||||||
|
final result = await ref
|
||||||
|
.read(selectedContainerProvider.notifier)
|
||||||
|
.setContainerId(container.id);
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (result == SetContainerResult.successHasProxy) {
|
||||||
|
final shouldStartProxy = await ref
|
||||||
|
.read(startProxyControllerProvider.notifier)
|
||||||
|
.shouldPromptProxyStart();
|
||||||
|
|
||||||
|
if (context.mounted && shouldStartProxy) {
|
||||||
|
final dialogResult = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const TorDialog(),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (context.mounted) {
|
if (dialogResult == true) {
|
||||||
ref
|
await ref
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
.read(startProxyControllerProvider.notifier)
|
||||||
.requestDismiss();
|
.startProxy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const BrowserRoute().go(context);
|
if (context.mounted &&
|
||||||
}
|
result != SetContainerResult.failed) {
|
||||||
},
|
const TabViewRoute().go(context);
|
||||||
),
|
}
|
||||||
FeedSearch(searchTextNotifier: sampledSearchText),
|
},
|
||||||
HistorySuggestions(
|
),
|
||||||
searchTextListenable: sampledSearchText,
|
],
|
||||||
onUriSelected: (uri) async {
|
if (!showNoInputSections) ...[
|
||||||
if (isEditMode) {
|
FullSearchTermSuggestions(
|
||||||
// Load into existing tab
|
searchTextController: searchTextController,
|
||||||
await ref
|
activeBang: activeBang,
|
||||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
submitSearch: submitSearch,
|
||||||
.loadUrl(url: uri);
|
domain: isEditMode ? existingTabState.url.host : null,
|
||||||
} else {
|
),
|
||||||
// Create new tab
|
TabSearch(searchTextListenable: sampledSearchText),
|
||||||
await ref
|
BookmarkSearch(
|
||||||
.read(tabRepositoryProvider.notifier)
|
searchTextListenable: sampledSearchText,
|
||||||
.addTab(
|
onUriSelected: (uri) async {
|
||||||
url: uri,
|
if (isEditMode) {
|
||||||
tabMode: effectiveTabMode,
|
await ref
|
||||||
parentId: (selectedTabType.value == TabType.child)
|
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||||
? ref.read(selectedTabProvider)
|
.loadUrl(url: uri);
|
||||||
: null,
|
} else {
|
||||||
launchedFromIntent: launchedFromIntent,
|
await ref
|
||||||
selectTab: true,
|
.read(tabRepositoryProvider.notifier)
|
||||||
containerSelection: selectedContainer == null
|
.addTab(
|
||||||
? const TabContainerSelection.unassigned()
|
url: uri,
|
||||||
: TabContainerSelection.specific(
|
tabMode: effectiveTabMode,
|
||||||
selectedContainer,
|
parentId: (selectedTabType.value == TabType.child)
|
||||||
),
|
? ref.read(selectedTabProvider)
|
||||||
);
|
: null,
|
||||||
}
|
launchedFromIntent: launchedFromIntent,
|
||||||
|
selectTab: true,
|
||||||
|
containerSelection: selectedContainer == null
|
||||||
|
? const TabContainerSelection.unassigned()
|
||||||
|
: TabContainerSelection.specific(
|
||||||
|
selectedContainer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ref
|
ref
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
.requestDismiss();
|
.requestDismiss();
|
||||||
|
|
||||||
const BrowserRoute().go(context);
|
const BrowserRoute().go(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
FeedSearch(searchTextNotifier: sampledSearchText),
|
||||||
|
HistorySuggestions(
|
||||||
|
searchTextListenable: sampledSearchText,
|
||||||
|
onUriSelected: (uri) async {
|
||||||
|
if (isEditMode) {
|
||||||
|
// Load into existing tab
|
||||||
|
await ref
|
||||||
|
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||||
|
.loadUrl(url: uri);
|
||||||
|
} else {
|
||||||
|
// Create new tab
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(
|
||||||
|
url: uri,
|
||||||
|
tabMode: effectiveTabMode,
|
||||||
|
parentId: (selectedTabType.value == TabType.child)
|
||||||
|
? ref.read(selectedTabProvider)
|
||||||
|
: null,
|
||||||
|
launchedFromIntent: launchedFromIntent,
|
||||||
|
selectTab: true,
|
||||||
|
containerSelection: selectedContainer == null
|
||||||
|
? const TabContainerSelection.unassigned()
|
||||||
|
: TabContainerSelection.specific(
|
||||||
|
selectedContainer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ref
|
||||||
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
|
.requestDismiss();
|
||||||
|
|
||||||
|
const BrowserRoute().go(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+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.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
private const val HISTORY_METADATA_MAX_AGE_IN_MS = 14L * 24 * 60 * 60 * 1000 // 14 days
|
||||||
|
|
||||||
object GlobalComponents {
|
object GlobalComponents {
|
||||||
private var _components: Components? = null
|
private var _components: Components? = null
|
||||||
private var currentMode: ComponentsMode? = null
|
private var currentMode: ComponentsMode? = null
|
||||||
@@ -203,6 +205,10 @@ object GlobalComponents {
|
|||||||
try {
|
try {
|
||||||
GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage)
|
GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage)
|
||||||
|
|
||||||
|
newComponents.core.historyMetadataService.cleanup(
|
||||||
|
System.currentTimeMillis() - HISTORY_METADATA_MAX_AGE_IN_MS,
|
||||||
|
)
|
||||||
|
|
||||||
GlobalAddonDependencyProvider.initialize(
|
GlobalAddonDependencyProvider.initialize(
|
||||||
newComponents.core.addonManager,
|
newComponents.core.addonManager,
|
||||||
newComponents.core.addonUpdater,
|
newComponents.core.addonUpdater,
|
||||||
|
|||||||
+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.GlobalComponents
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.HistoryHighlight
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.HistoryHighlightWeights
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo
|
import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.VisitType
|
import eu.weblibre.flutter_mozilla_components.pigeons.VisitType
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -206,4 +208,32 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getHistoryHighlights(
|
||||||
|
weights: HistoryHighlightWeights,
|
||||||
|
limit: Long,
|
||||||
|
callback: (Result<List<HistoryHighlight>>) -> Unit
|
||||||
|
) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val conceptWeights = mozilla.components.concept.storage.HistoryHighlightWeights(
|
||||||
|
viewTime = weights.viewTime,
|
||||||
|
frequency = weights.frequency,
|
||||||
|
)
|
||||||
|
val highlights = components.core.historyStorage.getHistoryHighlights(
|
||||||
|
conceptWeights,
|
||||||
|
limit.toInt(),
|
||||||
|
).map {
|
||||||
|
HistoryHighlight(
|
||||||
|
score = it.score,
|
||||||
|
placeId = it.placeId.toLong(),
|
||||||
|
url = it.url,
|
||||||
|
title = it.title,
|
||||||
|
previewImageUrl = it.previewImageUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
callback(Result.success(highlights))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+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.R
|
||||||
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
||||||
import eu.weblibre.flutter_mozilla_components.middleware.FlutterEventMiddleware
|
import eu.weblibre.flutter_mozilla_components.middleware.FlutterEventMiddleware
|
||||||
|
import eu.weblibre.flutter_mozilla_components.middleware.HistoryMetadataMiddleware
|
||||||
|
import eu.weblibre.flutter_mozilla_components.middleware.HistoryMetadataService
|
||||||
import eu.weblibre.flutter_mozilla_components.middleware.SaveToPDFMiddleware
|
import eu.weblibre.flutter_mozilla_components.middleware.SaveToPDFMiddleware
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
|
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
|
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
|
||||||
@@ -193,10 +195,15 @@ class Core(
|
|||||||
FileUploadsDirCleaner { context.cacheDir }
|
FileUploadsDirCleaner { context.cacheDir }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val historyMetadataService by lazy {
|
||||||
|
HistoryMetadataService(storage = historyStorage)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
val store by lazy {
|
val store by lazy {
|
||||||
BrowserStore(
|
BrowserStore(
|
||||||
middleware = listOf(
|
middleware = listOf(
|
||||||
|
HistoryMetadataMiddleware(historyMetadataService),
|
||||||
FlutterEventMiddleware(flutterEvents),
|
FlutterEventMiddleware(flutterEvents),
|
||||||
DownloadMiddleware(context, DownloadService::class.java, { false }),
|
DownloadMiddleware(context, DownloadService::class.java, { false }),
|
||||||
ThumbnailsMiddleware(thumbnailStorage),
|
ThumbnailsMiddleware(thumbnailStorage),
|
||||||
|
|||||||
+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()
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class HistoryHighlightWeights (
|
||||||
|
val viewTime: Double,
|
||||||
|
val frequency: Double
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): HistoryHighlightWeights {
|
||||||
|
val viewTime = pigeonVar_list[0] as Double
|
||||||
|
val frequency = pigeonVar_list[1] as Double
|
||||||
|
return HistoryHighlightWeights(viewTime, frequency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
viewTime,
|
||||||
|
frequency,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is HistoryHighlightWeights) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return GeckoPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class HistoryHighlight (
|
||||||
|
val score: Double,
|
||||||
|
val placeId: Long,
|
||||||
|
val url: String,
|
||||||
|
val title: String? = null,
|
||||||
|
val previewImageUrl: String? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): HistoryHighlight {
|
||||||
|
val score = pigeonVar_list[0] as Double
|
||||||
|
val placeId = pigeonVar_list[1] as Long
|
||||||
|
val url = pigeonVar_list[2] as String
|
||||||
|
val title = pigeonVar_list[3] as String?
|
||||||
|
val previewImageUrl = pigeonVar_list[4] as String?
|
||||||
|
return HistoryHighlight(score, placeId, url, title, previewImageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
score,
|
||||||
|
placeId,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
previewImageUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is HistoryHighlight) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return GeckoPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
/** Generated class from Pigeon that represents data sent in messages. */
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
data class HistoryItem (
|
data class HistoryItem (
|
||||||
val url: String,
|
val url: String,
|
||||||
@@ -3985,235 +4056,245 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
|||||||
}
|
}
|
||||||
185.toByte() -> {
|
185.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
HistoryItem.fromList(it)
|
HistoryHighlightWeights.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
186.toByte() -> {
|
186.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
HistoryState.fromList(it)
|
HistoryHighlight.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
187.toByte() -> {
|
187.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ReaderableState.fromList(it)
|
HistoryItem.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
188.toByte() -> {
|
188.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SecurityInfoState.fromList(it)
|
HistoryState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
189.toByte() -> {
|
189.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
TabContentState.fromList(it)
|
ReaderableState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
190.toByte() -> {
|
190.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
FindResultState.fromList(it)
|
SecurityInfoState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
191.toByte() -> {
|
191.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
CustomSelectionAction.fromList(it)
|
TabContentState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
192.toByte() -> {
|
192.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
WebExtensionData.fromList(it)
|
FindResultState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
193.toByte() -> {
|
193.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeckoSuggestion.fromList(it)
|
CustomSelectionAction.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
194.toByte() -> {
|
194.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
TabContent.fromList(it)
|
WebExtensionData.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
195.toByte() -> {
|
195.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ContentBlocking.fromList(it)
|
GeckoSuggestion.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
196.toByte() -> {
|
196.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
DohSettings.fromList(it)
|
TabContent.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
197.toByte() -> {
|
197.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeckoEngineSettings.fromList(it)
|
ContentBlocking.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
198.toByte() -> {
|
198.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
AutocompleteResult.fromList(it)
|
DohSettings.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
199.toByte() -> {
|
199.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
UnknownHitResult.fromList(it)
|
GeckoEngineSettings.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
200.toByte() -> {
|
200.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ImageHitResult.fromList(it)
|
AutocompleteResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
201.toByte() -> {
|
201.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
VideoHitResult.fromList(it)
|
UnknownHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
202.toByte() -> {
|
202.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
AudioHitResult.fromList(it)
|
ImageHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
203.toByte() -> {
|
203.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ImageSrcHitResult.fromList(it)
|
VideoHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
204.toByte() -> {
|
204.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PhoneHitResult.fromList(it)
|
AudioHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
205.toByte() -> {
|
205.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
EmailHitResult.fromList(it)
|
ImageSrcHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
206.toByte() -> {
|
206.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeoHitResult.fromList(it)
|
PhoneHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
207.toByte() -> {
|
207.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
DownloadState.fromList(it)
|
EmailHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
208.toByte() -> {
|
208.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ShareInternetResourceState.fromList(it)
|
GeoHitResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
209.toByte() -> {
|
209.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
AddonCollection.fromList(it)
|
DownloadState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
210.toByte() -> {
|
210.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncEngineStatus.fromList(it)
|
ShareInternetResourceState.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
211.toByte() -> {
|
211.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncAccountInfo.fromList(it)
|
AddonCollection.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
212.toByte() -> {
|
212.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncDevice.fromList(it)
|
SyncEngineStatus.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
213.toByte() -> {
|
213.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncIncomingTab.fromList(it)
|
SyncAccountInfo.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
214.toByte() -> {
|
214.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncRemoteTab.fromList(it)
|
SyncDevice.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
215.toByte() -> {
|
215.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncDeviceTabs.fromList(it)
|
SyncIncomingTab.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
216.toByte() -> {
|
216.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeckoPref.fromList(it)
|
SyncRemoteTab.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
217.toByte() -> {
|
217.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
MlProgressData.fromList(it)
|
SyncDeviceTabs.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
218.toByte() -> {
|
218.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ContainerSiteAssignment.fromList(it)
|
GeckoPref.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
219.toByte() -> {
|
219.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeckoHeader.fromList(it)
|
MlProgressData.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
220.toByte() -> {
|
220.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeckoFetchRequest.fromList(it)
|
ContainerSiteAssignment.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
221.toByte() -> {
|
221.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
GeckoFetchResponse.fromList(it)
|
GeckoHeader.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
222.toByte() -> {
|
222.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
BookmarkNode.fromList(it)
|
GeckoFetchRequest.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
223.toByte() -> {
|
223.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
BookmarkInfo.fromList(it)
|
GeckoFetchResponse.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
224.toByte() -> {
|
224.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SitePermissions.fromList(it)
|
BookmarkNode.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
225.toByte() -> {
|
225.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
TrackingProtectionException.fromList(it)
|
BookmarkInfo.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
226.toByte() -> {
|
226.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PwaIcon.fromList(it)
|
SitePermissions.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
227.toByte() -> {
|
227.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ShareTargetFiles.fromList(it)
|
TrackingProtectionException.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
228.toByte() -> {
|
228.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ShareTargetParams.fromList(it)
|
PwaIcon.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
229.toByte() -> {
|
229.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ShareTarget.fromList(it)
|
ShareTargetFiles.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
230.toByte() -> {
|
230.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
ExternalApplicationResource.fromList(it)
|
ShareTargetParams.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
231.toByte() -> {
|
231.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
ShareTarget.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
232.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
ExternalApplicationResource.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
233.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PwaManifest.fromList(it)
|
PwaManifest.fromList(it)
|
||||||
}
|
}
|
||||||
@@ -4447,194 +4528,202 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
|||||||
stream.write(184)
|
stream.write(184)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is HistoryItem -> {
|
is HistoryHighlightWeights -> {
|
||||||
stream.write(185)
|
stream.write(185)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is HistoryState -> {
|
is HistoryHighlight -> {
|
||||||
stream.write(186)
|
stream.write(186)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ReaderableState -> {
|
is HistoryItem -> {
|
||||||
stream.write(187)
|
stream.write(187)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SecurityInfoState -> {
|
is HistoryState -> {
|
||||||
stream.write(188)
|
stream.write(188)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is TabContentState -> {
|
is ReaderableState -> {
|
||||||
stream.write(189)
|
stream.write(189)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is FindResultState -> {
|
is SecurityInfoState -> {
|
||||||
stream.write(190)
|
stream.write(190)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is CustomSelectionAction -> {
|
is TabContentState -> {
|
||||||
stream.write(191)
|
stream.write(191)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is WebExtensionData -> {
|
is FindResultState -> {
|
||||||
stream.write(192)
|
stream.write(192)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeckoSuggestion -> {
|
is CustomSelectionAction -> {
|
||||||
stream.write(193)
|
stream.write(193)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is TabContent -> {
|
is WebExtensionData -> {
|
||||||
stream.write(194)
|
stream.write(194)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ContentBlocking -> {
|
is GeckoSuggestion -> {
|
||||||
stream.write(195)
|
stream.write(195)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is DohSettings -> {
|
is TabContent -> {
|
||||||
stream.write(196)
|
stream.write(196)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeckoEngineSettings -> {
|
is ContentBlocking -> {
|
||||||
stream.write(197)
|
stream.write(197)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is AutocompleteResult -> {
|
is DohSettings -> {
|
||||||
stream.write(198)
|
stream.write(198)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is UnknownHitResult -> {
|
is GeckoEngineSettings -> {
|
||||||
stream.write(199)
|
stream.write(199)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ImageHitResult -> {
|
is AutocompleteResult -> {
|
||||||
stream.write(200)
|
stream.write(200)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is VideoHitResult -> {
|
is UnknownHitResult -> {
|
||||||
stream.write(201)
|
stream.write(201)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is AudioHitResult -> {
|
is ImageHitResult -> {
|
||||||
stream.write(202)
|
stream.write(202)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ImageSrcHitResult -> {
|
is VideoHitResult -> {
|
||||||
stream.write(203)
|
stream.write(203)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is PhoneHitResult -> {
|
is AudioHitResult -> {
|
||||||
stream.write(204)
|
stream.write(204)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is EmailHitResult -> {
|
is ImageSrcHitResult -> {
|
||||||
stream.write(205)
|
stream.write(205)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeoHitResult -> {
|
is PhoneHitResult -> {
|
||||||
stream.write(206)
|
stream.write(206)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is DownloadState -> {
|
is EmailHitResult -> {
|
||||||
stream.write(207)
|
stream.write(207)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ShareInternetResourceState -> {
|
is GeoHitResult -> {
|
||||||
stream.write(208)
|
stream.write(208)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is AddonCollection -> {
|
is DownloadState -> {
|
||||||
stream.write(209)
|
stream.write(209)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncEngineStatus -> {
|
is ShareInternetResourceState -> {
|
||||||
stream.write(210)
|
stream.write(210)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncAccountInfo -> {
|
is AddonCollection -> {
|
||||||
stream.write(211)
|
stream.write(211)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncDevice -> {
|
is SyncEngineStatus -> {
|
||||||
stream.write(212)
|
stream.write(212)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncIncomingTab -> {
|
is SyncAccountInfo -> {
|
||||||
stream.write(213)
|
stream.write(213)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncRemoteTab -> {
|
is SyncDevice -> {
|
||||||
stream.write(214)
|
stream.write(214)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncDeviceTabs -> {
|
is SyncIncomingTab -> {
|
||||||
stream.write(215)
|
stream.write(215)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeckoPref -> {
|
is SyncRemoteTab -> {
|
||||||
stream.write(216)
|
stream.write(216)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is MlProgressData -> {
|
is SyncDeviceTabs -> {
|
||||||
stream.write(217)
|
stream.write(217)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ContainerSiteAssignment -> {
|
is GeckoPref -> {
|
||||||
stream.write(218)
|
stream.write(218)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeckoHeader -> {
|
is MlProgressData -> {
|
||||||
stream.write(219)
|
stream.write(219)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeckoFetchRequest -> {
|
is ContainerSiteAssignment -> {
|
||||||
stream.write(220)
|
stream.write(220)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is GeckoFetchResponse -> {
|
is GeckoHeader -> {
|
||||||
stream.write(221)
|
stream.write(221)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is BookmarkNode -> {
|
is GeckoFetchRequest -> {
|
||||||
stream.write(222)
|
stream.write(222)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is BookmarkInfo -> {
|
is GeckoFetchResponse -> {
|
||||||
stream.write(223)
|
stream.write(223)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SitePermissions -> {
|
is BookmarkNode -> {
|
||||||
stream.write(224)
|
stream.write(224)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is TrackingProtectionException -> {
|
is BookmarkInfo -> {
|
||||||
stream.write(225)
|
stream.write(225)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is PwaIcon -> {
|
is SitePermissions -> {
|
||||||
stream.write(226)
|
stream.write(226)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ShareTargetFiles -> {
|
is TrackingProtectionException -> {
|
||||||
stream.write(227)
|
stream.write(227)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ShareTargetParams -> {
|
is PwaIcon -> {
|
||||||
stream.write(228)
|
stream.write(228)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ShareTarget -> {
|
is ShareTargetFiles -> {
|
||||||
stream.write(229)
|
stream.write(229)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is ExternalApplicationResource -> {
|
is ShareTargetParams -> {
|
||||||
stream.write(230)
|
stream.write(230)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is PwaManifest -> {
|
is ShareTarget -> {
|
||||||
stream.write(231)
|
stream.write(231)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
|
is ExternalApplicationResource -> {
|
||||||
|
stream.write(232)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is PwaManifest -> {
|
||||||
|
stream.write(233)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
else -> super.writeValue(stream, value)
|
else -> super.writeValue(stream, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7531,6 +7620,7 @@ interface GeckoHistoryApi {
|
|||||||
fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit)
|
fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit)
|
||||||
fun deleteDownload(id: String, callback: (Result<Unit>) -> Unit)
|
fun deleteDownload(id: String, callback: (Result<Unit>) -> Unit)
|
||||||
fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit)
|
fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit)
|
||||||
|
fun getHistoryHighlights(weights: HistoryHighlightWeights, limit: Long, callback: (Result<List<HistoryHighlight>>) -> Unit)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** The codec used by GeckoHistoryApi. */
|
/** The codec used by GeckoHistoryApi. */
|
||||||
@@ -7644,6 +7734,27 @@ interface GeckoHistoryApi {
|
|||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.getHistoryHighlights$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val weightsArg = args[0] as HistoryHighlightWeights
|
||||||
|
val limitArg = args[1] as Long
|
||||||
|
api.getHistoryHighlights(weightsArg, limitArg) { result: Result<List<HistoryHighlight>> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ export 'src/pigeons/gecko.g.dart'
|
|||||||
GeckoSuggestionType,
|
GeckoSuggestionType,
|
||||||
GeckoTrackingProtectionApi,
|
GeckoTrackingProtectionApi,
|
||||||
GeoHitResult,
|
GeoHitResult,
|
||||||
|
HistoryHighlight,
|
||||||
|
HistoryHighlightWeights,
|
||||||
HistoryMetadataKey,
|
HistoryMetadataKey,
|
||||||
HitResult,
|
HitResult,
|
||||||
HttpsOnlyMode,
|
HttpsOnlyMode,
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ class GeckoEventService extends GeckoStateEvents {
|
|||||||
final _tabAddedSubject = PublishSubject<String>();
|
final _tabAddedSubject = PublishSubject<String>();
|
||||||
final _mlProgressSubject = PublishSubject<MlProgressData>();
|
final _mlProgressSubject = PublishSubject<MlProgressData>();
|
||||||
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
|
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
|
||||||
final _translationEngineSubject =
|
final _translationEngineSubject = BehaviorSubject<TranslationEngineEvent>();
|
||||||
BehaviorSubject<TranslationEngineEvent>();
|
|
||||||
final _tabTranslationSubject = ReplaySubject<TabTranslationEvent>();
|
final _tabTranslationSubject = ReplaySubject<TabTranslationEvent>();
|
||||||
|
|
||||||
// Event streams
|
// Event streams
|
||||||
|
|||||||
@@ -60,4 +60,11 @@ class GeckoHistoryService {
|
|||||||
end.millisecondsSinceEpoch,
|
end.millisecondsSinceEpoch,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<HistoryHighlight>> getHistoryHighlights({
|
||||||
|
required HistoryHighlightWeights weights,
|
||||||
|
required int limit,
|
||||||
|
}) {
|
||||||
|
return _api.getHistoryHighlights(weights, limit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -534,6 +534,29 @@ class VisitInfo {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HistoryHighlightWeights {
|
||||||
|
final double viewTime;
|
||||||
|
final double frequency;
|
||||||
|
|
||||||
|
HistoryHighlightWeights(this.viewTime, this.frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HistoryHighlight {
|
||||||
|
final double score;
|
||||||
|
final int placeId;
|
||||||
|
final String url;
|
||||||
|
final String? title;
|
||||||
|
final String? previewImageUrl;
|
||||||
|
|
||||||
|
HistoryHighlight(
|
||||||
|
this.score,
|
||||||
|
this.placeId,
|
||||||
|
this.url,
|
||||||
|
this.title,
|
||||||
|
this.previewImageUrl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class HistoryItem {
|
class HistoryItem {
|
||||||
final String url;
|
final String url;
|
||||||
final String title;
|
final String title;
|
||||||
@@ -1639,10 +1662,7 @@ abstract class GeckoStateEvents {
|
|||||||
int sequence,
|
int sequence,
|
||||||
TranslationEngineStateData state,
|
TranslationEngineStateData state,
|
||||||
);
|
);
|
||||||
void onTabTranslationStateChange(
|
void onTabTranslationStateChange(int sequence, TabTranslationStateData state);
|
||||||
int sequence,
|
|
||||||
TabTranslationStateData state,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FlutterApi()
|
@FlutterApi()
|
||||||
@@ -1797,6 +1817,12 @@ abstract class GeckoHistoryApi {
|
|||||||
|
|
||||||
@async
|
@async
|
||||||
void deleteVisitsBetween(int startMillis, int endMillis);
|
void deleteVisitsBetween(int startMillis, int endMillis);
|
||||||
|
|
||||||
|
@async
|
||||||
|
List<HistoryHighlight> getHistoryHighlights(
|
||||||
|
HistoryHighlightWeights weights,
|
||||||
|
int limit,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostApi()
|
@HostApi()
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ PlatformException _createConnectionError(String channelName) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
List<Object?> wrapResponse({
|
||||||
|
Object? result,
|
||||||
|
PlatformException? error,
|
||||||
|
bool empty = false,
|
||||||
|
}) {
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -24,37 +28,48 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed
|
a.indexed.every(
|
||||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
return a.length == b.length &&
|
||||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
a.entries.every(
|
||||||
_deepEquals(entry.value, b[entry.key]));
|
(MapEntry<Object?, Object?> entry) =>
|
||||||
|
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||||
|
_deepEquals(entry.value, b[entry.key]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Transport types for Tor connections
|
/// Transport types for Tor connections
|
||||||
enum TransportType {
|
enum TransportType {
|
||||||
/// Direct Tor connection (no bridges)
|
/// Direct Tor connection (no bridges)
|
||||||
none,
|
none,
|
||||||
|
|
||||||
/// obfs4 pluggable transport
|
/// obfs4 pluggable transport
|
||||||
obfs4,
|
obfs4,
|
||||||
|
|
||||||
/// Snowflake pluggable transport (default broker)
|
/// Snowflake pluggable transport (default broker)
|
||||||
snowflake,
|
snowflake,
|
||||||
|
|
||||||
/// Snowflake via AMP cache
|
/// Snowflake via AMP cache
|
||||||
snowflakeAmp,
|
snowflakeAmp,
|
||||||
|
|
||||||
/// Meek pluggable transport
|
/// Meek pluggable transport
|
||||||
meek,
|
meek,
|
||||||
|
|
||||||
/// Meek via Azure CDN
|
/// Meek via Azure CDN
|
||||||
meekAzure,
|
meekAzure,
|
||||||
|
|
||||||
/// WebTunnel pluggable transport
|
/// WebTunnel pluggable transport
|
||||||
webtunnel,
|
webtunnel,
|
||||||
|
|
||||||
/// Custom bridge lines (passthrough)
|
/// Custom bridge lines (passthrough)
|
||||||
custom,
|
custom,
|
||||||
}
|
}
|
||||||
@@ -95,7 +110,8 @@ class TorConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList(); }
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
static TorConfiguration decode(Object result) {
|
static TorConfiguration decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -122,8 +138,7 @@ class TorConfiguration {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList())
|
int get hashCode => Object.hashAll(_toList());
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current Tor status
|
/// Current Tor status
|
||||||
@@ -162,7 +177,8 @@ class TorStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList(); }
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
static TorStatus decode(Object result) {
|
static TorStatus decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -189,8 +205,7 @@ class TorStatus {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList())
|
int get hashCode => Object.hashAll(_toList());
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log message from Tor
|
/// Log message from Tor
|
||||||
@@ -211,15 +226,12 @@ class TorLogMessage {
|
|||||||
int timestamp;
|
int timestamp;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[
|
return <Object?>[severity, message, timestamp];
|
||||||
severity,
|
|
||||||
message,
|
|
||||||
timestamp,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList(); }
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
static TorLogMessage decode(Object result) {
|
static TorLogMessage decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -244,11 +256,9 @@ class TorLogMessage {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList())
|
int get hashCode => Object.hashAll(_toList());
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -256,16 +266,16 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is TransportType) {
|
} else if (value is TransportType) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.index);
|
writeValue(buffer, value.index);
|
||||||
} else if (value is TorConfiguration) {
|
} else if (value is TorConfiguration) {
|
||||||
buffer.putUint8(130);
|
buffer.putUint8(130);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is TorStatus) {
|
} else if (value is TorStatus) {
|
||||||
buffer.putUint8(131);
|
buffer.putUint8(131);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is TorLogMessage) {
|
} else if (value is TorLogMessage) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -276,14 +286,14 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
@override
|
@override
|
||||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 129:
|
case 129:
|
||||||
final value = readValue(buffer) as int?;
|
final value = readValue(buffer) as int?;
|
||||||
return value == null ? null : TransportType.values[value];
|
return value == null ? null : TransportType.values[value];
|
||||||
case 130:
|
case 130:
|
||||||
return TorConfiguration.decode(readValue(buffer)!);
|
return TorConfiguration.decode(readValue(buffer)!);
|
||||||
case 131:
|
case 131:
|
||||||
return TorStatus.decode(readValue(buffer)!);
|
return TorStatus.decode(readValue(buffer)!);
|
||||||
case 132:
|
case 132:
|
||||||
return TorLogMessage.decode(readValue(buffer)!);
|
return TorLogMessage.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
@@ -297,8 +307,10 @@ class TorApi {
|
|||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -308,13 +320,16 @@ class TorApi {
|
|||||||
/// Start Tor with the given configuration
|
/// Start Tor with the given configuration
|
||||||
/// Returns a Future to avoid blocking the main thread
|
/// Returns a Future to avoid blocking the main thread
|
||||||
Future<int> startTor(TorConfiguration config) async {
|
Future<int> startTor(TorConfiguration config) async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[config]);
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||||
|
<Object?>[config],
|
||||||
|
);
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
@@ -336,7 +351,8 @@ class TorApi {
|
|||||||
|
|
||||||
/// Stop Tor
|
/// Stop Tor
|
||||||
Future<void> stopTor() async {
|
Future<void> stopTor() async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -359,7 +375,8 @@ class TorApi {
|
|||||||
|
|
||||||
/// Get current status
|
/// Get current status
|
||||||
Future<TorStatus> getStatus() async {
|
Future<TorStatus> getStatus() async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -387,7 +404,8 @@ class TorApi {
|
|||||||
|
|
||||||
/// Request a new Tor identity (new circuit)
|
/// Request a new Tor identity (new circuit)
|
||||||
Future<void> requestNewIdentity() async {
|
Future<void> requestNewIdentity() async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -419,54 +437,76 @@ abstract class TorLogApi {
|
|||||||
/// Called when status changes
|
/// Called when status changes
|
||||||
void onStatusChanged(TorStatus status);
|
void onStatusChanged(TorStatus status);
|
||||||
|
|
||||||
static void setUp(TorLogApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
static void setUp(
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
TorLogApi? api, {
|
||||||
|
BinaryMessenger? binaryMessenger,
|
||||||
|
String messageChannelSuffix = '',
|
||||||
|
}) {
|
||||||
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix', pigeonChannelCodec,
|
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix',
|
||||||
binaryMessenger: binaryMessenger);
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: binaryMessenger,
|
||||||
|
);
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(message != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.');
|
message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.',
|
||||||
|
);
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final TorLogMessage? arg_log = (args[0] as TorLogMessage?);
|
final TorLogMessage? arg_log = (args[0] as TorLogMessage?);
|
||||||
assert(arg_log != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.');
|
arg_log != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.',
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
api.onLogMessage(arg_log!);
|
api.onLogMessage(arg_log!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
return wrapResponse(
|
||||||
|
error: PlatformException(code: 'error', message: e.toString()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix', pigeonChannelCodec,
|
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix',
|
||||||
binaryMessenger: binaryMessenger);
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: binaryMessenger,
|
||||||
|
);
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(message != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.');
|
message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.',
|
||||||
|
);
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final TorStatus? arg_status = (args[0] as TorStatus?);
|
final TorStatus? arg_status = (args[0] as TorStatus?);
|
||||||
assert(arg_status != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.');
|
arg_status != null,
|
||||||
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.',
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
api.onStatusChanged(arg_status!);
|
api.onStatusChanged(arg_status!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
return wrapResponse(
|
||||||
|
error: PlatformException(code: 'error', message: e.toString()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -478,9 +518,13 @@ class IPtProxyController {
|
|||||||
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
IPtProxyController({
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
BinaryMessenger? binaryMessenger,
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
String messageChannelSuffix = '',
|
||||||
|
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -488,13 +532,16 @@ class IPtProxyController {
|
|||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<int> start(TransportType proxyType, String proxy) async {
|
Future<int> start(TransportType proxyType, String proxy) async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType, proxy]);
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||||
|
<Object?>[proxyType, proxy],
|
||||||
|
);
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
@@ -515,13 +562,16 @@ class IPtProxyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop(TransportType proxyType) async {
|
Future<void> stop(TransportType proxyType) async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType]);
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||||
|
<Object?>[proxyType],
|
||||||
|
);
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
|||||||
@@ -14,40 +14,39 @@ PlatformException _createConnectionError(String channelName) {
|
|||||||
message: 'Unable to establish connection on channel: "$channelName".',
|
message: 'Unable to establish connection on channel: "$channelName".',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed
|
a.indexed.every(
|
||||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
return a.length == b.length &&
|
||||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
a.entries.every(
|
||||||
_deepEquals(entry.value, b[entry.key]));
|
(MapEntry<Object?, Object?> entry) =>
|
||||||
|
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||||
|
_deepEquals(entry.value, b[entry.key]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LocalizedResult {
|
class LocalizedResult {
|
||||||
LocalizedResult({
|
LocalizedResult({required this.languageName, this.countryName});
|
||||||
required this.languageName,
|
|
||||||
this.countryName,
|
|
||||||
});
|
|
||||||
|
|
||||||
String languageName;
|
String languageName;
|
||||||
|
|
||||||
String? countryName;
|
String? countryName;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[
|
return <Object?>[languageName, countryName];
|
||||||
languageName,
|
|
||||||
countryName,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList(); }
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
static LocalizedResult decode(Object result) {
|
static LocalizedResult decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -71,11 +70,9 @@ class LocalizedResult {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList())
|
int get hashCode => Object.hashAll(_toList());
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -83,7 +80,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is LocalizedResult) {
|
} else if (value is LocalizedResult) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -94,7 +91,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
@override
|
@override
|
||||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 129:
|
case 129:
|
||||||
return LocalizedResult.decode(readValue(buffer)!);
|
return LocalizedResult.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
@@ -106,23 +103,33 @@ class LocaleResolver {
|
|||||||
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
LocaleResolver({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
LocaleResolver({
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
BinaryMessenger? binaryMessenger,
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
String messageChannelSuffix = '',
|
||||||
|
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<LocalizedResult> resolve(String languageTag, String targetLangouageTag) async {
|
Future<LocalizedResult> resolve(
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
String languageTag,
|
||||||
|
String targetLangouageTag,
|
||||||
|
) async {
|
||||||
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[languageTag, targetLangouageTag]);
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||||
|
<Object?>[languageTag, targetLangouageTag],
|
||||||
|
);
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
|||||||
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
List<Object?> wrapResponse({
|
||||||
|
Object? result,
|
||||||
|
PlatformException? error,
|
||||||
|
bool empty = false,
|
||||||
|
}) {
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -17,21 +21,25 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed
|
a.indexed.every(
|
||||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
return a.length == b.length &&
|
||||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
a.entries.every(
|
||||||
_deepEquals(entry.value, b[entry.key]));
|
(MapEntry<Object?, Object?> entry) =>
|
||||||
|
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||||
|
_deepEquals(entry.value, b[entry.key]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Intent {
|
class Intent {
|
||||||
Intent({
|
Intent({
|
||||||
this.fromPackageName,
|
this.fromPackageName,
|
||||||
@@ -66,7 +74,8 @@ class Intent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList(); }
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
static Intent decode(Object result) {
|
static Intent decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -94,11 +103,9 @@ class Intent {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList())
|
int get hashCode => Object.hashAll(_toList());
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -106,7 +113,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is Intent) {
|
} else if (value is Intent) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
@@ -117,7 +124,7 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
@override
|
@override
|
||||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 129:
|
case 129:
|
||||||
return Intent.decode(readValue(buffer)!);
|
return Intent.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
@@ -130,32 +137,48 @@ abstract class IntentEvents {
|
|||||||
|
|
||||||
void onIntentReceived(int sequence, Intent intent);
|
void onIntentReceived(int sequence, Intent intent);
|
||||||
|
|
||||||
static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
static void setUp(
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
IntentEvents? api, {
|
||||||
|
BinaryMessenger? binaryMessenger,
|
||||||
|
String messageChannelSuffix = '',
|
||||||
|
}) {
|
||||||
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix', pigeonChannelCodec,
|
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix',
|
||||||
binaryMessenger: binaryMessenger);
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: binaryMessenger,
|
||||||
|
);
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(message != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.');
|
message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.',
|
||||||
|
);
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final int? arg_sequence = (args[0] as int?);
|
final int? arg_sequence = (args[0] as int?);
|
||||||
assert(arg_sequence != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.');
|
arg_sequence != null,
|
||||||
|
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.',
|
||||||
|
);
|
||||||
final Intent? arg_intent = (args[1] as Intent?);
|
final Intent? arg_intent = (args[1] as Intent?);
|
||||||
assert(arg_intent != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.');
|
arg_intent != null,
|
||||||
|
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.',
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
api.onIntentReceived(arg_sequence!, arg_intent!);
|
api.onIntentReceived(arg_sequence!, arg_intent!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
return wrapResponse(
|
||||||
|
error: PlatformException(code: 'error', message: e.toString()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ PlatformException _createConnectionError(String channelName) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
List<Object?> wrapResponse({
|
||||||
|
Object? result,
|
||||||
|
PlatformException? error,
|
||||||
|
bool empty = false,
|
||||||
|
}) {
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -25,7 +29,6 @@ List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty
|
|||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -52,9 +55,13 @@ class SpeechToTextApi {
|
|||||||
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
SpeechToTextApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
SpeechToTextApi({
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
BinaryMessenger? binaryMessenger,
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
String messageChannelSuffix = '',
|
||||||
|
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -69,13 +76,16 @@ class SpeechToTextApi {
|
|||||||
/// The [locale] parameter specifies the language locale for recognition
|
/// The [locale] parameter specifies the language locale for recognition
|
||||||
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
||||||
Future<bool> showDialog({String? locale}) async {
|
Future<bool> showDialog({String? locale}) async {
|
||||||
final pigeonVar_channelName = 'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[locale]);
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||||
|
<Object?>[locale],
|
||||||
|
);
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
@@ -106,29 +116,43 @@ abstract class SpeechToTextEvents {
|
|||||||
/// recognition failed or was cancelled.
|
/// recognition failed or was cancelled.
|
||||||
void onTextReceived(String text);
|
void onTextReceived(String text);
|
||||||
|
|
||||||
static void setUp(SpeechToTextEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
static void setUp(
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
SpeechToTextEvents? api, {
|
||||||
|
BinaryMessenger? binaryMessenger,
|
||||||
|
String messageChannelSuffix = '',
|
||||||
|
}) {
|
||||||
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||||
|
? '.$messageChannelSuffix'
|
||||||
|
: '';
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix', pigeonChannelCodec,
|
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix',
|
||||||
binaryMessenger: binaryMessenger);
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: binaryMessenger,
|
||||||
|
);
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(message != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.');
|
message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.',
|
||||||
|
);
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final String? arg_text = (args[0] as String?);
|
final String? arg_text = (args[0] as String?);
|
||||||
assert(arg_text != null,
|
assert(
|
||||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.');
|
arg_text != null,
|
||||||
|
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.',
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
api.onTextReceived(arg_text!);
|
api.onTextReceived(arg_text!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
return wrapResponse(
|
||||||
|
error: PlatformException(code: 'error', message: e.toString()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user