added many suggestion modules
This commit is contained in:
@@ -66,8 +66,7 @@ class TranslationState with FastEquatable {
|
||||
displayError: data.displayError,
|
||||
);
|
||||
|
||||
bool get hasError =>
|
||||
translationErrorName != null && (displayError ?? true);
|
||||
bool get hasError => translationErrorName != null && (displayError ?? true);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
|
||||
@@ -136,6 +136,7 @@ class BookmarkSearchResults extends _$BookmarkSearchResults {
|
||||
}
|
||||
|
||||
final results = await _service.searchBookmarks(query, limit: limit);
|
||||
if (!ref.mounted) return;
|
||||
state = results
|
||||
.map(BookmarkItem.parseRecursive)
|
||||
.whereType<BookmarkEntry>()
|
||||
|
||||
@@ -86,7 +86,7 @@ final class BookmarkSearchResultsProvider
|
||||
}
|
||||
|
||||
String _$bookmarkSearchResultsHash() =>
|
||||
r'f1413466db26c1e3407501453b8cec97504a771d';
|
||||
r'44af94b2234c817b08b0bde2dc68f9ab8d2d0a9a';
|
||||
|
||||
abstract class _$BookmarkSearchResults extends $Notifier<List<BookmarkEntry>> {
|
||||
List<BookmarkEntry> build();
|
||||
|
||||
@@ -47,7 +47,7 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
typedef TabStateWirthContainer = (TabState, ContainerData?);
|
||||
typedef TabStateWithContainer = (TabState, ContainerData?);
|
||||
|
||||
@Riverpod()
|
||||
bool canManualTabReorder(Ref ref) {
|
||||
@@ -175,7 +175,7 @@ EquatableValue<Map<String, TabState>> containerTabStates(
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> fifoTabStates(Ref ref) {
|
||||
final containerData = ref
|
||||
.watch(watchContainersWithCountProvider.select((value) => value.value))
|
||||
.mapNotNull(
|
||||
@@ -202,7 +202,7 @@ EquatableValue<List<TabStateWirthContainer>> fifoTabStates(Ref ref) {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
selectedContainerTabStatesWithContainer(Ref ref) {
|
||||
final filter = ref.watch(
|
||||
selectedContainerProvider.select(
|
||||
@@ -256,7 +256,7 @@ selectedContainerTabStatesWithContainer(Ref ref) {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableValue<List<TabStateWirthContainer>> quickTabSwitcherTabStates(
|
||||
EquatableValue<List<TabStateWithContainer>> quickTabSwitcherTabStates(
|
||||
Ref ref,
|
||||
QuickTabSwitcherMode mode,
|
||||
) {
|
||||
|
||||
@@ -430,11 +430,11 @@ final fifoTabStatesProvider = FifoTabStatesProvider._();
|
||||
final class FifoTabStatesProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
>
|
||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
||||
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||
FifoTabStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -451,30 +451,30 @@ final class FifoTabStatesProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
||||
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||
return fifoTabStates(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(
|
||||
EquatableValue<List<TabStateWirthContainer>> value,
|
||||
EquatableValue<List<TabStateWithContainer>> value,
|
||||
) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$fifoTabStatesHash() => r'03298f30559d55e986d97a17d517b9a9abb89be4';
|
||||
String _$fifoTabStatesHash() => r'f8dc69382d307bc83193a3b839a16313e9c1ab69';
|
||||
|
||||
@ProviderFor(selectedContainerTabStatesWithContainer)
|
||||
final selectedContainerTabStatesWithContainerProvider =
|
||||
@@ -483,11 +483,11 @@ final selectedContainerTabStatesWithContainerProvider =
|
||||
final class SelectedContainerTabStatesWithContainerProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
>
|
||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
||||
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||
SelectedContainerTabStatesWithContainerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -505,23 +505,23 @@ final class SelectedContainerTabStatesWithContainerProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
||||
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||
return selectedContainerTabStatesWithContainer(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(
|
||||
EquatableValue<List<TabStateWirthContainer>> value,
|
||||
EquatableValue<List<TabStateWithContainer>> value,
|
||||
) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
@@ -529,7 +529,7 @@ final class SelectedContainerTabStatesWithContainerProvider
|
||||
}
|
||||
|
||||
String _$selectedContainerTabStatesWithContainerHash() =>
|
||||
r'e2dc85fc72ee29736d8fdaabb1abadd23cc4bac0';
|
||||
r'187e408857ddc5fe08f8e5344beb510c3c39abe1';
|
||||
|
||||
@ProviderFor(quickTabSwitcherTabStates)
|
||||
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
||||
@@ -537,11 +537,11 @@ final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
||||
final class QuickTabSwitcherTabStatesProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWirthContainer>>
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>
|
||||
>
|
||||
with $Provider<EquatableValue<List<TabStateWirthContainer>>> {
|
||||
with $Provider<EquatableValue<List<TabStateWithContainer>>> {
|
||||
QuickTabSwitcherTabStatesProvider._({
|
||||
required QuickTabSwitcherTabStatesFamily super.from,
|
||||
required QuickTabSwitcherMode super.argument,
|
||||
@@ -565,24 +565,24 @@ final class QuickTabSwitcherTabStatesProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<List<TabStateWirthContainer>>> $createElement(
|
||||
$ProviderElement<EquatableValue<List<TabStateWithContainer>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<List<TabStateWirthContainer>> create(Ref ref) {
|
||||
EquatableValue<List<TabStateWithContainer>> create(Ref ref) {
|
||||
final argument = this.argument as QuickTabSwitcherMode;
|
||||
return quickTabSwitcherTabStates(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(
|
||||
EquatableValue<List<TabStateWirthContainer>> value,
|
||||
EquatableValue<List<TabStateWithContainer>> value,
|
||||
) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWirthContainer>>>(
|
||||
$SyncValueProvider<EquatableValue<List<TabStateWithContainer>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
@@ -601,12 +601,12 @@ final class QuickTabSwitcherTabStatesProvider
|
||||
}
|
||||
|
||||
String _$quickTabSwitcherTabStatesHash() =>
|
||||
r'df990fb96cca62a272572c925028725f81a0e96b';
|
||||
r'2627bcd25718472f0c8057e836d76bdaa7317c1c';
|
||||
|
||||
final class QuickTabSwitcherTabStatesFamily extends $Family
|
||||
with
|
||||
$FunctionalFamilyOverride<
|
||||
EquatableValue<List<TabStateWirthContainer>>,
|
||||
EquatableValue<List<TabStateWithContainer>>,
|
||||
QuickTabSwitcherMode
|
||||
> {
|
||||
QuickTabSwitcherTabStatesFamily._()
|
||||
|
||||
+3
-2
@@ -587,8 +587,9 @@ class _TranslatePageTile extends ConsumerWidget {
|
||||
tabStateProvider(selectedTabId).select((s) => s?.translationState),
|
||||
);
|
||||
final readerActive = ref.watch(
|
||||
tabStateProvider(selectedTabId)
|
||||
.select((s) => s?.readerableState.active ?? false),
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((s) => s?.readerableState.active ?? false),
|
||||
);
|
||||
|
||||
// Hide when reader mode is active (Fenix-aligned)
|
||||
|
||||
@@ -588,12 +588,14 @@ class TabMenu extends HookConsumerWidget {
|
||||
builder: (context, ref, child) {
|
||||
final engineState = ref.watch(translationEngineStateProvider);
|
||||
final translationState = ref.watch(
|
||||
tabStateProvider(selectedTabId)
|
||||
.select((s) => s?.translationState),
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((s) => s?.translationState),
|
||||
);
|
||||
final readerActive = ref.watch(
|
||||
tabStateProvider(selectedTabId)
|
||||
.select((s) => s?.readerableState.active ?? false),
|
||||
tabStateProvider(
|
||||
selectedTabId,
|
||||
).select((s) => s?.readerableState.active ?? false),
|
||||
);
|
||||
|
||||
// Hide when reader mode is active (Fenix-aligned)
|
||||
@@ -620,9 +622,7 @@ class TabMenu extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
isTranslated ? 'Translated' : 'Translate Page',
|
||||
),
|
||||
child: Text(isTranslated ? 'Translated' : 'Translate Page'),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -132,8 +132,10 @@ extension HitResultX on HitResult {
|
||||
HitResult withCleanedLink(String cleanedUrl) {
|
||||
return switch (this) {
|
||||
UnknownHitResult() => UnknownHitResult(src: cleanedUrl),
|
||||
ImageSrcHitResult(:final src) =>
|
||||
ImageSrcHitResult(src: src, uri: cleanedUrl),
|
||||
ImageSrcHitResult(:final src) => ImageSrcHitResult(
|
||||
src: src,
|
||||
uri: cleanedUrl,
|
||||
),
|
||||
_ => this,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,6 +56,20 @@ class HistoryRepository extends _$HistoryRepository {
|
||||
return _service.deleteVisit(info);
|
||||
}
|
||||
|
||||
Future<List<HistoryHighlight>> getHistoryHighlights({
|
||||
double viewTimeWeight = 10.0,
|
||||
double frequencyWeight = 4.0,
|
||||
required int limit,
|
||||
}) {
|
||||
return _service.getHistoryHighlights(
|
||||
weights: HistoryHighlightWeights(
|
||||
viewTime: viewTimeWeight,
|
||||
frequency: frequencyWeight,
|
||||
),
|
||||
limit: limit,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class HistoryRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$historyRepositoryHash() => r'f9ccc32275bb2f95edbe53b2245b4843e8d0c439';
|
||||
String _$historyRepositoryHash() => r'19f883fdb644e34bc89483b4a8ddb60f54c52114';
|
||||
|
||||
abstract class _$HistoryRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
import 'package:weblibre/features/web_feed/domain/providers.dart';
|
||||
|
||||
part 'empty_state_content.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
Future<List<HistoryHighlight>> searchEmptyHistoryHighlights(
|
||||
Ref ref, {
|
||||
int count = 25,
|
||||
}) async {
|
||||
final highlights = await ref
|
||||
.read(historyRepositoryProvider.notifier)
|
||||
.getHistoryHighlights(limit: count);
|
||||
|
||||
if (!ref.mounted) return [];
|
||||
|
||||
return highlights.where((h) => Uri.tryParse(h.url) != null).toList();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Future<List<VisitInfo>> searchEmptyRecentHistory(
|
||||
Ref ref, {
|
||||
int count = 25,
|
||||
}) async {
|
||||
final visits = await ref
|
||||
.read(historyRepositoryProvider.notifier)
|
||||
.getVisitsPaginated(count: count);
|
||||
|
||||
if (!ref.mounted) return [];
|
||||
|
||||
return visits.where((v) => Uri.tryParse(v.url) != null).toList();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
AsyncValue<List<FeedArticle>> searchEmptyRecentFeedArticles(
|
||||
Ref ref, {
|
||||
int count = 25,
|
||||
}) {
|
||||
final feeds = ref.watch(feedListProvider);
|
||||
|
||||
final hasFeeds = feeds.whenOrNull(data: (list) => list.isNotEmpty) ?? false;
|
||||
if (!hasFeeds) {
|
||||
return const AsyncValue.data([]);
|
||||
}
|
||||
|
||||
return ref
|
||||
.watch(feedArticleListProvider(null))
|
||||
.whenData((articles) => articles.take(count).toList());
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
List<TabStateWithContainer> searchEmptyRecentTabs(Ref ref, {int count = 25}) {
|
||||
final tabs = ref.watch(fifoTabStatesProvider).value;
|
||||
|
||||
return tabs.take(count).toList();
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'empty_state_content.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(searchEmptyHistoryHighlights)
|
||||
final searchEmptyHistoryHighlightsProvider =
|
||||
SearchEmptyHistoryHighlightsFamily._();
|
||||
|
||||
final class SearchEmptyHistoryHighlightsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<HistoryHighlight>>,
|
||||
List<HistoryHighlight>,
|
||||
FutureOr<List<HistoryHighlight>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<HistoryHighlight>>,
|
||||
$FutureProvider<List<HistoryHighlight>> {
|
||||
SearchEmptyHistoryHighlightsProvider._({
|
||||
required SearchEmptyHistoryHighlightsFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'searchEmptyHistoryHighlightsProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$searchEmptyHistoryHighlightsHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'searchEmptyHistoryHighlightsProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<HistoryHighlight>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<HistoryHighlight>> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return searchEmptyHistoryHighlights(ref, count: argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SearchEmptyHistoryHighlightsProvider &&
|
||||
other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$searchEmptyHistoryHighlightsHash() =>
|
||||
r'f12206cb05c5c03ecdc2b84e3ffd20706aa4c986';
|
||||
|
||||
final class SearchEmptyHistoryHighlightsFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<List<HistoryHighlight>>, int> {
|
||||
SearchEmptyHistoryHighlightsFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'searchEmptyHistoryHighlightsProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
SearchEmptyHistoryHighlightsProvider call({int count = 25}) =>
|
||||
SearchEmptyHistoryHighlightsProvider._(argument: count, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'searchEmptyHistoryHighlightsProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(searchEmptyRecentHistory)
|
||||
final searchEmptyRecentHistoryProvider = SearchEmptyRecentHistoryFamily._();
|
||||
|
||||
final class SearchEmptyRecentHistoryProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<VisitInfo>>,
|
||||
List<VisitInfo>,
|
||||
FutureOr<List<VisitInfo>>
|
||||
>
|
||||
with $FutureModifier<List<VisitInfo>>, $FutureProvider<List<VisitInfo>> {
|
||||
SearchEmptyRecentHistoryProvider._({
|
||||
required SearchEmptyRecentHistoryFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'searchEmptyRecentHistoryProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$searchEmptyRecentHistoryHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'searchEmptyRecentHistoryProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<VisitInfo>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<VisitInfo>> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return searchEmptyRecentHistory(ref, count: argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SearchEmptyRecentHistoryProvider &&
|
||||
other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$searchEmptyRecentHistoryHash() =>
|
||||
r'bd7b9a67edfb1751d6d5cf6a8392516911796cb0';
|
||||
|
||||
final class SearchEmptyRecentHistoryFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<List<VisitInfo>>, int> {
|
||||
SearchEmptyRecentHistoryFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'searchEmptyRecentHistoryProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
SearchEmptyRecentHistoryProvider call({int count = 25}) =>
|
||||
SearchEmptyRecentHistoryProvider._(argument: count, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'searchEmptyRecentHistoryProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(searchEmptyRecentFeedArticles)
|
||||
final searchEmptyRecentFeedArticlesProvider =
|
||||
SearchEmptyRecentFeedArticlesFamily._();
|
||||
|
||||
final class SearchEmptyRecentFeedArticlesProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>
|
||||
>
|
||||
with $Provider<AsyncValue<List<FeedArticle>>> {
|
||||
SearchEmptyRecentFeedArticlesProvider._({
|
||||
required SearchEmptyRecentFeedArticlesFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'searchEmptyRecentFeedArticlesProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$searchEmptyRecentFeedArticlesHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'searchEmptyRecentFeedArticlesProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<AsyncValue<List<FeedArticle>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
AsyncValue<List<FeedArticle>> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return searchEmptyRecentFeedArticles(ref, count: argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(AsyncValue<List<FeedArticle>> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<AsyncValue<List<FeedArticle>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SearchEmptyRecentFeedArticlesProvider &&
|
||||
other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$searchEmptyRecentFeedArticlesHash() =>
|
||||
r'ee6ac7336d58b78c6c9295cdbb8aeeca9a91c2ad';
|
||||
|
||||
final class SearchEmptyRecentFeedArticlesFamily extends $Family
|
||||
with $FunctionalFamilyOverride<AsyncValue<List<FeedArticle>>, int> {
|
||||
SearchEmptyRecentFeedArticlesFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'searchEmptyRecentFeedArticlesProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
SearchEmptyRecentFeedArticlesProvider call({int count = 25}) =>
|
||||
SearchEmptyRecentFeedArticlesProvider._(argument: count, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'searchEmptyRecentFeedArticlesProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(searchEmptyRecentTabs)
|
||||
final searchEmptyRecentTabsProvider = SearchEmptyRecentTabsFamily._();
|
||||
|
||||
final class SearchEmptyRecentTabsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
List<TabStateWithContainer>,
|
||||
List<TabStateWithContainer>,
|
||||
List<TabStateWithContainer>
|
||||
>
|
||||
with $Provider<List<TabStateWithContainer>> {
|
||||
SearchEmptyRecentTabsProvider._({
|
||||
required SearchEmptyRecentTabsFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'searchEmptyRecentTabsProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$searchEmptyRecentTabsHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'searchEmptyRecentTabsProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<List<TabStateWithContainer>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
List<TabStateWithContainer> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return searchEmptyRecentTabs(ref, count: argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(List<TabStateWithContainer> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<List<TabStateWithContainer>>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SearchEmptyRecentTabsProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$searchEmptyRecentTabsHash() =>
|
||||
r'6000970dd261f243326900555967956508b84660';
|
||||
|
||||
final class SearchEmptyRecentTabsFamily extends $Family
|
||||
with $FunctionalFamilyOverride<List<TabStateWithContainer>, int> {
|
||||
SearchEmptyRecentTabsFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'searchEmptyRecentTabsProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
SearchEmptyRecentTabsProvider call({int count = 25}) =>
|
||||
SearchEmptyRecentTabsProvider._(argument: count, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'searchEmptyRecentTabsProvider';
|
||||
}
|
||||
+11
-1
@@ -21,7 +21,17 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'search_modules_view.g.dart';
|
||||
|
||||
enum SearchModuleType { tabs, articles, bookmarks, history }
|
||||
enum SearchModuleType {
|
||||
tabs,
|
||||
articles,
|
||||
bookmarks,
|
||||
history,
|
||||
historyHighlights,
|
||||
recentHistory,
|
||||
recentArticles,
|
||||
recentTabs,
|
||||
containers,
|
||||
}
|
||||
|
||||
enum SearchModuleDisplayState { preview, expanded, collapsed }
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/containers_section.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/history_highlights_section.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_feed_articles_section.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_history_section.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/empty_state/recent_tabs_section.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart';
|
||||
@@ -45,6 +50,8 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/isolatio
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart';
|
||||
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
|
||||
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
|
||||
@@ -135,6 +142,17 @@ class SearchScreen extends HookConsumerWidget {
|
||||
source: searchTextController,
|
||||
sampleDuration: const Duration(milliseconds: 150),
|
||||
);
|
||||
final hasUserProvidedInput = useState(
|
||||
initialSearchText?.isNotEmpty == true,
|
||||
);
|
||||
|
||||
useOnListenableChange(searchTextController, () {
|
||||
hasUserProvidedInput.value = searchTextController.text.isNotEmpty;
|
||||
});
|
||||
|
||||
final showNoInputSections =
|
||||
!hasUserProvidedInput.value && searchTextController.text.isEmpty;
|
||||
|
||||
final searchFocusNode = useFocusNode();
|
||||
final textFieldKey = useMemoized(() => GlobalKey());
|
||||
final preferredHeight = useState<double>(kToolbarHeight);
|
||||
@@ -484,86 +502,211 @@ class SearchScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: ClipboardFillLink(controller: searchTextController),
|
||||
),
|
||||
FullSearchTermSuggestions(
|
||||
searchTextController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
submitSearch: submitSearch,
|
||||
domain: isEditMode ? existingTabState.url.host : null,
|
||||
),
|
||||
TabSearch(searchTextListenable: sampledSearchText),
|
||||
BookmarkSearch(
|
||||
searchTextListenable: sampledSearchText,
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
if (showNoInputSections) ...[
|
||||
HistoryHighlightsSection(
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: uri,
|
||||
tabMode: effectiveTabMode,
|
||||
parentId: (selectedTabType.value == TabType.child)
|
||||
? ref.read(selectedTabProvider)
|
||||
: null,
|
||||
launchedFromIntent: launchedFromIntent,
|
||||
selectTab: true,
|
||||
containerSelection: selectedContainer == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
: TabContainerSelection.specific(
|
||||
selectedContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
RecentFeedArticlesSection(
|
||||
onArticleSelected: (article) {
|
||||
FeedArticleRoute(articleId: article.id).go(context);
|
||||
},
|
||||
),
|
||||
RecentTabsSection(
|
||||
onTabSelected: (tabId) async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: uri,
|
||||
tabMode: effectiveTabMode,
|
||||
parentId: (selectedTabType.value == TabType.child)
|
||||
? ref.read(selectedTabProvider)
|
||||
: null,
|
||||
launchedFromIntent: launchedFromIntent,
|
||||
selectTab: true,
|
||||
containerSelection: selectedContainer == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
: TabContainerSelection.specific(
|
||||
selectedContainer,
|
||||
),
|
||||
.selectTab(tabId);
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
RecentHistorySection(
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: uri,
|
||||
tabMode: effectiveTabMode,
|
||||
parentId: (selectedTabType.value == TabType.child)
|
||||
? ref.read(selectedTabProvider)
|
||||
: null,
|
||||
launchedFromIntent: launchedFromIntent,
|
||||
selectTab: true,
|
||||
containerSelection: selectedContainer == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
: TabContainerSelection.specific(
|
||||
selectedContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
ContainersSection(
|
||||
onContainerSelected: (container) async {
|
||||
final result = await ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (result == SetContainerResult.successHasProxy) {
|
||||
final shouldStartProxy = await ref
|
||||
.read(startProxyControllerProvider.notifier)
|
||||
.shouldPromptProxyStart();
|
||||
|
||||
if (context.mounted && shouldStartProxy) {
|
||||
final dialogResult = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => const TorDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
if (dialogResult == true) {
|
||||
await ref
|
||||
.read(startProxyControllerProvider.notifier)
|
||||
.startProxy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
FeedSearch(searchTextNotifier: sampledSearchText),
|
||||
HistorySuggestions(
|
||||
searchTextListenable: sampledSearchText,
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
// Load into existing tab
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
// Create new tab
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: uri,
|
||||
tabMode: effectiveTabMode,
|
||||
parentId: (selectedTabType.value == TabType.child)
|
||||
? ref.read(selectedTabProvider)
|
||||
: null,
|
||||
launchedFromIntent: launchedFromIntent,
|
||||
selectTab: true,
|
||||
containerSelection: selectedContainer == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
: TabContainerSelection.specific(
|
||||
selectedContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted &&
|
||||
result != SetContainerResult.failed) {
|
||||
const TabViewRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
if (!showNoInputSections) ...[
|
||||
FullSearchTermSuggestions(
|
||||
searchTextController: searchTextController,
|
||||
activeBang: activeBang,
|
||||
submitSearch: submitSearch,
|
||||
domain: isEditMode ? existingTabState.url.host : null,
|
||||
),
|
||||
TabSearch(searchTextListenable: sampledSearchText),
|
||||
BookmarkSearch(
|
||||
searchTextListenable: sampledSearchText,
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: uri,
|
||||
tabMode: effectiveTabMode,
|
||||
parentId: (selectedTabType.value == TabType.child)
|
||||
? ref.read(selectedTabProvider)
|
||||
: null,
|
||||
launchedFromIntent: launchedFromIntent,
|
||||
selectTab: true,
|
||||
containerSelection: selectedContainer == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
: TabContainerSelection.specific(
|
||||
selectedContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
FeedSearch(searchTextNotifier: sampledSearchText),
|
||||
HistorySuggestions(
|
||||
searchTextListenable: sampledSearchText,
|
||||
onUriSelected: (uri) async {
|
||||
if (isEditMode) {
|
||||
// Load into existing tab
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: tabId).notifier)
|
||||
.loadUrl(url: uri);
|
||||
} else {
|
||||
// Create new tab
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: uri,
|
||||
tabMode: effectiveTabMode,
|
||||
parentId: (selectedTabType.value == TabType.child)
|
||||
? ref.read(selectedTabProvider)
|
||||
: null,
|
||||
launchedFromIntent: launchedFromIntent,
|
||||
selectTab: true,
|
||||
containerSelection: selectedContainer == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
: TabContainerSelection.specific(
|
||||
selectedContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
|
||||
const BrowserRoute().go(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||
|
||||
class ContainersSection extends StatelessWidget {
|
||||
final void Function(ContainerDataWithCount container) onContainerSelected;
|
||||
|
||||
const ContainersSection({super.key, required this.onContainerSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SearchModuleSection(
|
||||
title: 'Containers',
|
||||
moduleType: SearchModuleType.containers,
|
||||
totalCount: 1,
|
||||
contentSliverBuilder:
|
||||
({required bool isCollapsed, required int visibleCount}) => [
|
||||
if (!isCollapsed)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: ContainerChips(
|
||||
selectedContainer: null,
|
||||
onSelected: (container) {
|
||||
if (container != null) {
|
||||
onContainerSelected(container);
|
||||
}
|
||||
},
|
||||
onDeleted: null,
|
||||
displayMenu: false,
|
||||
showUnassignedChip: false,
|
||||
enableDragAndDrop: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_list_tile.dart';
|
||||
|
||||
class HistoryHighlightsSection extends ConsumerWidget {
|
||||
final void Function(Uri uri) onUriSelected;
|
||||
|
||||
const HistoryHighlightsSection({super.key, required this.onUriSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final highlightsAsync = ref.watch(searchEmptyHistoryHighlightsProvider());
|
||||
final highlights = highlightsAsync.value ?? [];
|
||||
|
||||
if (highlightsAsync.hasValue && highlights.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
return SearchModuleSection(
|
||||
title: 'History Highlights',
|
||||
moduleType: SearchModuleType.historyHighlights,
|
||||
totalCount: highlights.length,
|
||||
contentSliverBuilder:
|
||||
({required bool isCollapsed, required int visibleCount}) => [
|
||||
if (!isCollapsed)
|
||||
SliverList.builder(
|
||||
itemCount: visibleCount,
|
||||
itemBuilder: (context, index) {
|
||||
final highlight = highlights[index];
|
||||
final uri = Uri.parse(highlight.url);
|
||||
|
||||
return UrlListTile(
|
||||
title: highlight.title ?? uri.authority,
|
||||
uri: uri,
|
||||
onTap: () => onUriSelected(uri),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/core/providers/format.dart';
|
||||
import 'package:weblibre/extensions/uri.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_link.dart';
|
||||
import 'package:weblibre/features/web_feed/extensions/atom.dart';
|
||||
import 'package:weblibre/features/web_feed/extensions/feed_article.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
|
||||
class RecentFeedArticlesSection extends ConsumerWidget {
|
||||
final void Function(FeedArticle article) onArticleSelected;
|
||||
|
||||
const RecentFeedArticlesSection({super.key, required this.onArticleSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final articlesAsync = ref.watch(searchEmptyRecentFeedArticlesProvider());
|
||||
final articles = articlesAsync.value ?? [];
|
||||
|
||||
if (articlesAsync.hasValue && articles.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
return SearchModuleSection(
|
||||
title: 'Recent Articles',
|
||||
moduleType: SearchModuleType.recentArticles,
|
||||
totalCount: articles.length,
|
||||
contentSliverBuilder:
|
||||
({required bool isCollapsed, required int visibleCount}) => [
|
||||
if (!isCollapsed)
|
||||
SliverList.builder(
|
||||
itemCount: visibleCount,
|
||||
itemBuilder: (context, index) {
|
||||
final article = articles[index];
|
||||
final articleDate = article.updated ?? article.created;
|
||||
|
||||
return ListTile(
|
||||
leading: RepaintBoundary(
|
||||
child: UrlIcon([
|
||||
article.icon ??
|
||||
article.links
|
||||
?.getRelation(FeedLinkRelation.alternate)
|
||||
?.uri ??
|
||||
article.siteLink ??
|
||||
article.feedId.base,
|
||||
], iconSize: 24.0),
|
||||
),
|
||||
title: Text(
|
||||
article.displayTitle,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (article.summaryPlain != null)
|
||||
Text(
|
||||
article.summaryPlain!,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (articleDate != null)
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text(
|
||||
ref
|
||||
.read(formatProvider.notifier)
|
||||
.fullDateTime(articleDate),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(fontStyle: FontStyle.italic),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => onArticleSelected(article),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_list_tile.dart';
|
||||
|
||||
class RecentHistorySection extends ConsumerWidget {
|
||||
final void Function(Uri uri) onUriSelected;
|
||||
|
||||
const RecentHistorySection({super.key, required this.onUriSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final historyAsync = ref.watch(searchEmptyRecentHistoryProvider());
|
||||
final visits = historyAsync.value ?? [];
|
||||
|
||||
if (historyAsync.hasValue && visits.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
return SearchModuleSection(
|
||||
title: 'Recent History',
|
||||
moduleType: SearchModuleType.recentHistory,
|
||||
totalCount: visits.length,
|
||||
contentSliverBuilder:
|
||||
({required bool isCollapsed, required int visibleCount}) => [
|
||||
if (!isCollapsed)
|
||||
SliverList.builder(
|
||||
itemCount: visibleCount,
|
||||
itemBuilder: (context, index) {
|
||||
final visit = visits[index];
|
||||
final uri = Uri.parse(visit.url);
|
||||
|
||||
return UrlListTile(
|
||||
title: visit.title ?? uri.authority,
|
||||
uri: uri,
|
||||
onTap: () => onUriSelected(uri),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/empty_state_content.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_list_tile.dart';
|
||||
|
||||
class RecentTabsSection extends ConsumerWidget {
|
||||
final void Function(String tabId) onTabSelected;
|
||||
|
||||
const RecentTabsSection({super.key, required this.onTabSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tabs = ref.watch(searchEmptyRecentTabsProvider());
|
||||
|
||||
if (tabs.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
return SearchModuleSection(
|
||||
title: 'Recent Tabs',
|
||||
moduleType: SearchModuleType.recentTabs,
|
||||
totalCount: tabs.length,
|
||||
contentSliverBuilder:
|
||||
({required bool isCollapsed, required int visibleCount}) => [
|
||||
if (!isCollapsed)
|
||||
SliverList.builder(
|
||||
itemCount: visibleCount,
|
||||
itemBuilder: (context, index) {
|
||||
final (tabState, containerData) = tabs[index];
|
||||
|
||||
return UrlListTile(
|
||||
title: tabState.titleOrAuthority,
|
||||
uri: tabState.url,
|
||||
leading: TabIcon(
|
||||
tabState: tabState,
|
||||
iconSize: UrlListTile.iconSize,
|
||||
),
|
||||
borderColor: containerData?.color,
|
||||
onTap: () => onTabSelected(tabState.id),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user