basic history working
This commit is contained in:
@@ -28,6 +28,7 @@ part of 'routes.dart';
|
|||||||
path: 'search/:tabType/:searchText',
|
path: 'search/:tabType/:searchText',
|
||||||
),
|
),
|
||||||
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
|
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
|
||||||
|
TypedGoRoute<HistoryRoute>(name: 'HistoryRoute', path: 'history'),
|
||||||
TypedGoRoute<ContextMenuRoute>(
|
TypedGoRoute<ContextMenuRoute>(
|
||||||
name: 'ContextMenuRoute',
|
name: 'ContextMenuRoute',
|
||||||
path: 'context_menu',
|
path: 'context_menu',
|
||||||
@@ -178,3 +179,10 @@ class OpenSharedContentRoute extends GoRouteData with _$OpenSharedContentRoute {
|
|||||||
return DialogPage(builder: (_) => OpenSharedContent(sharedUrl: $extra));
|
return DialogPage(builder: (_) => OpenSharedContent(sharedUrl: $extra));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HistoryRoute extends GoRouteData with _$HistoryRoute {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
|
return const HistoryScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/dialog
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/history/presentation/screens/history.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/search/presentation/screens/search.dart';
|
import 'package:weblibre/features/geckoview/features/search/presentation/screens/search.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart';
|
||||||
|
|||||||
@@ -295,6 +295,12 @@ RouteBase get $browserRoute => GoRouteData.$route(
|
|||||||
|
|
||||||
factory: _$TorProxyRoute._fromState,
|
factory: _$TorProxyRoute._fromState,
|
||||||
),
|
),
|
||||||
|
GoRouteData.$route(
|
||||||
|
path: 'history',
|
||||||
|
name: 'HistoryRoute',
|
||||||
|
|
||||||
|
factory: _$HistoryRoute._fromState,
|
||||||
|
),
|
||||||
GoRouteData.$route(
|
GoRouteData.$route(
|
||||||
path: 'context_menu',
|
path: 'context_menu',
|
||||||
name: 'ContextMenuRoute',
|
name: 'ContextMenuRoute',
|
||||||
@@ -419,6 +425,26 @@ mixin _$TorProxyRoute on GoRouteData {
|
|||||||
void replace(BuildContext context) => context.replace(location);
|
void replace(BuildContext context) => context.replace(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin _$HistoryRoute on GoRouteData {
|
||||||
|
static HistoryRoute _fromState(GoRouterState state) => HistoryRoute();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location('/history');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
|
|
||||||
mixin _$ContextMenuRoute on GoRouteData {
|
mixin _$ContextMenuRoute on GoRouteData {
|
||||||
static ContextMenuRoute _fromState(GoRouterState state) =>
|
static ContextMenuRoute _fromState(GoRouterState state) =>
|
||||||
ContextMenuRoute(state.extra as String);
|
ContextMenuRoute(state.extra as String);
|
||||||
|
|||||||
+7
@@ -257,6 +257,13 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
|||||||
leadingIcon: const Icon(Icons.settings),
|
leadingIcon: const Icon(Icons.settings),
|
||||||
child: const Text('Settings'),
|
child: const Text('Settings'),
|
||||||
),
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await HistoryRoute().push(context);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(Icons.history),
|
||||||
|
child: const Text('History'),
|
||||||
|
),
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, childRef, child) {
|
builder: (context, childRef, child) {
|
||||||
final browserExtensions = childRef.watch(
|
final browserExtensions = childRef.watch(
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
|
||||||
|
part 'history_filter_options.g.dart';
|
||||||
|
|
||||||
|
@CopyWith()
|
||||||
|
class HistoryFilterOptions with FastEquatable {
|
||||||
|
final DateTime? start;
|
||||||
|
final DateTime? end;
|
||||||
|
final Set<VisitType> visitTypes;
|
||||||
|
|
||||||
|
HistoryFilterOptions({
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
required this.visitTypes,
|
||||||
|
});
|
||||||
|
|
||||||
|
HistoryFilterOptions.withDefaults()
|
||||||
|
: this(
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
visitTypes: {VisitType.link, VisitType.typed},
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [start, end, visitTypes];
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'history_filter_options.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class _$HistoryFilterOptionsCWProxy {
|
||||||
|
HistoryFilterOptions start(DateTime? start);
|
||||||
|
|
||||||
|
HistoryFilterOptions end(DateTime? end);
|
||||||
|
|
||||||
|
HistoryFilterOptions visitTypes(Set<VisitType> visitTypes);
|
||||||
|
|
||||||
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `HistoryFilterOptions(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
|
///
|
||||||
|
/// Usage
|
||||||
|
/// ```dart
|
||||||
|
/// HistoryFilterOptions(...).copyWith(id: 12, name: "My name")
|
||||||
|
/// ````
|
||||||
|
HistoryFilterOptions call({
|
||||||
|
DateTime? start,
|
||||||
|
DateTime? end,
|
||||||
|
Set<VisitType> visitTypes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfHistoryFilterOptions.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfHistoryFilterOptions.copyWith.fieldName(...)`
|
||||||
|
class _$HistoryFilterOptionsCWProxyImpl
|
||||||
|
implements _$HistoryFilterOptionsCWProxy {
|
||||||
|
const _$HistoryFilterOptionsCWProxyImpl(this._value);
|
||||||
|
|
||||||
|
final HistoryFilterOptions _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HistoryFilterOptions start(DateTime? start) => this(start: start);
|
||||||
|
|
||||||
|
@override
|
||||||
|
HistoryFilterOptions end(DateTime? end) => this(end: end);
|
||||||
|
|
||||||
|
@override
|
||||||
|
HistoryFilterOptions visitTypes(Set<VisitType> visitTypes) =>
|
||||||
|
this(visitTypes: visitTypes);
|
||||||
|
|
||||||
|
@override
|
||||||
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `HistoryFilterOptions(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
|
///
|
||||||
|
/// Usage
|
||||||
|
/// ```dart
|
||||||
|
/// HistoryFilterOptions(...).copyWith(id: 12, name: "My name")
|
||||||
|
/// ````
|
||||||
|
HistoryFilterOptions call({
|
||||||
|
Object? start = const $CopyWithPlaceholder(),
|
||||||
|
Object? end = const $CopyWithPlaceholder(),
|
||||||
|
Object? visitTypes = const $CopyWithPlaceholder(),
|
||||||
|
}) {
|
||||||
|
return HistoryFilterOptions(
|
||||||
|
start: start == const $CopyWithPlaceholder()
|
||||||
|
? _value.start
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: start as DateTime?,
|
||||||
|
end: end == const $CopyWithPlaceholder()
|
||||||
|
? _value.end
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: end as DateTime?,
|
||||||
|
visitTypes: visitTypes == const $CopyWithPlaceholder()
|
||||||
|
? _value.visitTypes
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: visitTypes as Set<VisitType>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $HistoryFilterOptionsCopyWith on HistoryFilterOptions {
|
||||||
|
/// Returns a callable class that can be used as follows: `instanceOfHistoryFilterOptions.copyWith(...)` or like so:`instanceOfHistoryFilterOptions.copyWith.fieldName(...)`.
|
||||||
|
// ignore: library_private_types_in_public_api
|
||||||
|
_$HistoryFilterOptionsCWProxy get copyWith =>
|
||||||
|
_$HistoryFilterOptionsCWProxyImpl(this);
|
||||||
|
}
|
||||||
@@ -20,16 +20,38 @@
|
|||||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:riverpod/riverpod.dart';
|
import 'package:riverpod/riverpod.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/history/domain/entities/history_filter_options.dart';
|
||||||
|
|
||||||
part 'providers.g.dart';
|
part 'providers.g.dart';
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod(keepAlive: true)
|
||||||
Future<List<VisitInfo>> browsingHistory(
|
class HistoryFilter extends _$HistoryFilter {
|
||||||
Ref red, {
|
void updateVisitType(VisitType type, bool value) {
|
||||||
required DateTime start,
|
if (value) {
|
||||||
required DateTime end,
|
state = state.copyWith.visitTypes({...state.visitTypes, type});
|
||||||
required Set<VisitType> types,
|
} else {
|
||||||
}) {
|
state = state.copyWith.visitTypes({...state.visitTypes}..remove(type));
|
||||||
final service = GeckoHistoryService();
|
}
|
||||||
return service.getDetailedVisits(start, end, types);
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
HistoryFilterOptions build() {
|
||||||
|
return HistoryFilterOptions.withDefaults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Future<List<VisitInfo>> browsingHistory(Ref ref) {
|
||||||
|
final options = ref.watch(historyFilterProvider);
|
||||||
|
|
||||||
|
final service = GeckoHistoryService();
|
||||||
|
return service
|
||||||
|
.getDetailedVisits(
|
||||||
|
options.start ?? DateTime(0),
|
||||||
|
options.end ?? DateTime(9999),
|
||||||
|
options.visitTypes,
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(visits) => visits..sort((a, b) => b.visitTime.compareTo(a.visitTime)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,186 +6,39 @@ part of 'providers.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$browsingHistoryHash() => r'1f644757c60473f00dd94c9d312647baeaec525a';
|
String _$browsingHistoryHash() => r'a684c34fc370474a771c5fcc7982a200de2294e5';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
|
||||||
class _SystemHash {
|
|
||||||
_SystemHash._();
|
|
||||||
|
|
||||||
static int combine(int hash, int value) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + value);
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
|
||||||
return hash ^ (hash >> 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int finish(int hash) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = hash ^ (hash >> 11);
|
|
||||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [browsingHistory].
|
/// See also [browsingHistory].
|
||||||
@ProviderFor(browsingHistory)
|
@ProviderFor(browsingHistory)
|
||||||
const browsingHistoryProvider = BrowsingHistoryFamily();
|
final browsingHistoryProvider =
|
||||||
|
AutoDisposeFutureProvider<List<VisitInfo>>.internal(
|
||||||
/// See also [browsingHistory].
|
browsingHistory,
|
||||||
class BrowsingHistoryFamily extends Family<AsyncValue<List<VisitInfo>>> {
|
name: r'browsingHistoryProvider',
|
||||||
/// See also [browsingHistory].
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
const BrowsingHistoryFamily();
|
? null
|
||||||
|
: _$browsingHistoryHash,
|
||||||
/// See also [browsingHistory].
|
dependencies: null,
|
||||||
BrowsingHistoryProvider call({
|
allTransitiveDependencies: null,
|
||||||
required DateTime start,
|
|
||||||
required DateTime end,
|
|
||||||
required Set<VisitType> types,
|
|
||||||
}) {
|
|
||||||
return BrowsingHistoryProvider(start: start, end: end, types: types);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
BrowsingHistoryProvider getProviderOverride(
|
|
||||||
covariant BrowsingHistoryProvider provider,
|
|
||||||
) {
|
|
||||||
return call(
|
|
||||||
start: provider.start,
|
|
||||||
end: provider.end,
|
|
||||||
types: provider.types,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
|
||||||
_allTransitiveDependencies;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get name => r'browsingHistoryProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [browsingHistory].
|
|
||||||
class BrowsingHistoryProvider
|
|
||||||
extends AutoDisposeFutureProvider<List<VisitInfo>> {
|
|
||||||
/// See also [browsingHistory].
|
|
||||||
BrowsingHistoryProvider({
|
|
||||||
required DateTime start,
|
|
||||||
required DateTime end,
|
|
||||||
required Set<VisitType> types,
|
|
||||||
}) : this._internal(
|
|
||||||
(ref) => browsingHistory(
|
|
||||||
ref as BrowsingHistoryRef,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
types: types,
|
|
||||||
),
|
|
||||||
from: browsingHistoryProvider,
|
|
||||||
name: r'browsingHistoryProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$browsingHistoryHash,
|
|
||||||
dependencies: BrowsingHistoryFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
BrowsingHistoryFamily._allTransitiveDependencies,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
types: types,
|
|
||||||
);
|
|
||||||
|
|
||||||
BrowsingHistoryProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.start,
|
|
||||||
required this.end,
|
|
||||||
required this.types,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final DateTime start;
|
|
||||||
final DateTime end;
|
|
||||||
final Set<VisitType> types;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(
|
|
||||||
FutureOr<List<VisitInfo>> Function(BrowsingHistoryRef provider) create,
|
|
||||||
) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
override: BrowsingHistoryProvider._internal(
|
|
||||||
(ref) => create(ref as BrowsingHistoryRef),
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
types: types,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeFutureProviderElement<List<VisitInfo>> createElement() {
|
|
||||||
return _BrowsingHistoryProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is BrowsingHistoryProvider &&
|
|
||||||
other.start == start &&
|
|
||||||
other.end == end &&
|
|
||||||
other.types == types;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, start.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, end.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, types.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
mixin BrowsingHistoryRef on AutoDisposeFutureProviderRef<List<VisitInfo>> {
|
typedef BrowsingHistoryRef = AutoDisposeFutureProviderRef<List<VisitInfo>>;
|
||||||
/// The parameter `start` of this provider.
|
String _$historyFilterHash() => r'271d60ce79f96ac49d450f01d21cbfdad684ce53';
|
||||||
DateTime get start;
|
|
||||||
|
|
||||||
/// The parameter `end` of this provider.
|
/// See also [HistoryFilter].
|
||||||
DateTime get end;
|
@ProviderFor(HistoryFilter)
|
||||||
|
final historyFilterProvider =
|
||||||
/// The parameter `types` of this provider.
|
NotifierProvider<HistoryFilter, HistoryFilterOptions>.internal(
|
||||||
Set<VisitType> get types;
|
HistoryFilter.new,
|
||||||
}
|
name: r'historyFilterProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
class _BrowsingHistoryProviderElement
|
? null
|
||||||
extends AutoDisposeFutureProviderElement<List<VisitInfo>>
|
: _$historyFilterHash,
|
||||||
with BrowsingHistoryRef {
|
dependencies: null,
|
||||||
_BrowsingHistoryProviderElement(super.provider);
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
@override
|
|
||||||
DateTime get start => (origin as BrowsingHistoryProvider).start;
|
|
||||||
@override
|
|
||||||
DateTime get end => (origin as BrowsingHistoryProvider).end;
|
|
||||||
@override
|
|
||||||
Set<VisitType> get types => (origin as BrowsingHistoryProvider).types;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
typedef _$HistoryFilter = Notifier<HistoryFilterOptions>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|||||||
@@ -1,22 +1,204 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart' show DateFormat;
|
||||||
|
import 'package:nullability/nullability.dart';
|
||||||
|
import 'package:sliver_tools/sliver_tools.dart';
|
||||||
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
import 'package:weblibre/features/geckoview/features/history/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/history/domain/providers.dart';
|
||||||
|
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||||
|
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||||
|
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||||
|
|
||||||
|
class Section extends MultiSliver {
|
||||||
|
static final _datePattern = DateFormat('yMMMMd').addPattern('Hm');
|
||||||
|
|
||||||
|
Section({
|
||||||
|
Key? key,
|
||||||
|
required BuildContext context,
|
||||||
|
required String title,
|
||||||
|
required List<VisitInfo> items,
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
pushPinnedChildren: true,
|
||||||
|
children: [
|
||||||
|
SliverPinnedHeader(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.only(left: 24, top: 8),
|
||||||
|
color: Theme.of(context).canvasColor,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverList.builder(
|
||||||
|
itemCount: items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = items[index];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
key: ValueKey(item.hashCode),
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: UrlIcon([Uri.parse(item.url)], iconSize: 24),
|
||||||
|
title: item.title.mapNotNull((title) => Text(title)),
|
||||||
|
subtitle: Text(
|
||||||
|
item.url,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 54),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
children: [
|
||||||
|
Chip(
|
||||||
|
label: switch (item.visitType) {
|
||||||
|
VisitType.link => const Text('Followed Link'),
|
||||||
|
VisitType.typed => const Text('Typed Address'),
|
||||||
|
VisitType.embed => const Text(
|
||||||
|
'Embedded Page Element',
|
||||||
|
),
|
||||||
|
VisitType.redirectPermanent => const Text(
|
||||||
|
'Temporary Redirect',
|
||||||
|
),
|
||||||
|
VisitType.redirectTemporary => const Text(
|
||||||
|
'Permanent Redirect',
|
||||||
|
),
|
||||||
|
VisitType.download => const Text('Download'),
|
||||||
|
VisitType.framedLink => const Text('Frame'),
|
||||||
|
VisitType.reload => const Text('Page Reload'),
|
||||||
|
VisitType.bookmark => throw UnimplementedError(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Chip(
|
||||||
|
label: Text(
|
||||||
|
_datePattern.format(
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
item.visitTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class HistoryScreen extends HookConsumerWidget {
|
class HistoryScreen extends HookConsumerWidget {
|
||||||
const HistoryScreen({super.key});
|
const HistoryScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final history = ref.watch(
|
final historyFilter = ref.watch(historyFilterProvider);
|
||||||
browsingHistoryProvider(
|
|
||||||
start: DateTime(0),
|
final menuController = useMenuController();
|
||||||
end: DateTime.now(),
|
|
||||||
types: {
|
final historyEntries = ref.watch(browsingHistoryProvider);
|
||||||
VisitType.link,
|
|
||||||
VisitType.redirectPermanent,
|
return Scaffold(
|
||||||
VisitType.redirectTemporary,
|
appBar: AppBar(
|
||||||
VisitType.typed,
|
actions: [
|
||||||
|
MenuAnchor(
|
||||||
|
controller: menuController,
|
||||||
|
menuChildren: [
|
||||||
|
...VisitType.values
|
||||||
|
.whereNot(
|
||||||
|
(element) => const {VisitType.bookmark}.contains(element),
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(type) => CheckboxMenuButton(
|
||||||
|
value: historyFilter.visitTypes.contains(type),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref
|
||||||
|
.read(historyFilterProvider.notifier)
|
||||||
|
.updateVisitType(type, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: switch (type) {
|
||||||
|
VisitType.link => const Text('Followed Links'),
|
||||||
|
VisitType.typed => const Text('Typed Addresses'),
|
||||||
|
VisitType.embed => const Text('Embedded Page Elements'),
|
||||||
|
VisitType.redirectPermanent => const Text(
|
||||||
|
'Temporary Redirects',
|
||||||
|
),
|
||||||
|
VisitType.redirectTemporary => const Text(
|
||||||
|
'Permanent Redirects',
|
||||||
|
),
|
||||||
|
VisitType.download => const Text('Downloads'),
|
||||||
|
VisitType.framedLink => const Text('Frames'),
|
||||||
|
VisitType.reload => const Text('Page Reloads'),
|
||||||
|
VisitType.bookmark => throw UnimplementedError(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (menuController.isOpen) {
|
||||||
|
menuController.close();
|
||||||
|
} else {
|
||||||
|
menuController.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: historyEntries.when(
|
||||||
|
data: (data) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
// ignore: unused_result
|
||||||
|
await ref.refresh(browsingHistoryProvider.future);
|
||||||
|
},
|
||||||
|
child: HookBuilder(
|
||||||
|
builder: (context) {
|
||||||
|
final groups = useMemoized(
|
||||||
|
() => data.groupListsBy(
|
||||||
|
(element) => timeago.format(
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(element.visitTime),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[EquatableValue(data)],
|
||||||
|
);
|
||||||
|
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
for (final MapEntry(:key, :value) in groups.entries)
|
||||||
|
Section(context: context, title: key, items: value),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
error: (error, stackTrace) => Center(
|
||||||
|
child: FailureWidget(
|
||||||
|
title: 'Failed to load History',
|
||||||
|
exception: error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -167,14 +167,33 @@ enum class CookieSameSiteStatus(val raw: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class VisitType(val raw: Int) {
|
enum class VisitType(val raw: Int) {
|
||||||
|
/** The user followed a link and got a new toplevel window. */
|
||||||
LINK(0),
|
LINK(0),
|
||||||
|
/**
|
||||||
|
* The user typed the page's URL in the URL bar or selected it from
|
||||||
|
* URL bar autocomplete results, clicked on it from a history query
|
||||||
|
* (from the History sidebar, History menu, or history query in the
|
||||||
|
* personal toolbar or Places organizer.
|
||||||
|
*/
|
||||||
TYPED(1),
|
TYPED(1),
|
||||||
|
/** The user followed a bookmark to get to the page. */
|
||||||
BOOKMARK(2),
|
BOOKMARK(2),
|
||||||
|
/**
|
||||||
|
* Some inner content is loaded. This is true of all images on a
|
||||||
|
* page, and the contents of the iframe. It is also true of any
|
||||||
|
* content in a frame if the user did not explicitly follow a link
|
||||||
|
* to get there.
|
||||||
|
*/
|
||||||
EMBED(3),
|
EMBED(3),
|
||||||
|
/** Set when the transition was a permanent redirect. */
|
||||||
REDIRECT_PERMANENT(4),
|
REDIRECT_PERMANENT(4),
|
||||||
|
/** Set when the transition was a temporary redirect. */
|
||||||
REDIRECT_TEMPORARY(5),
|
REDIRECT_TEMPORARY(5),
|
||||||
|
/** Set when the transition is a download. */
|
||||||
DOWNLOAD(6),
|
DOWNLOAD(6),
|
||||||
|
/** The user followed a link and got a visit in a frame. */
|
||||||
FRAMED_LINK(7),
|
FRAMED_LINK(7),
|
||||||
|
/** The user reloaded a page. */
|
||||||
RELOAD(8);
|
RELOAD(8);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -93,14 +93,29 @@ enum CookieSameSiteStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VisitType {
|
enum VisitType {
|
||||||
|
/// The user followed a link and got a new toplevel window.
|
||||||
link,
|
link,
|
||||||
|
/// The user typed the page's URL in the URL bar or selected it from
|
||||||
|
/// URL bar autocomplete results, clicked on it from a history query
|
||||||
|
/// (from the History sidebar, History menu, or history query in the
|
||||||
|
/// personal toolbar or Places organizer.
|
||||||
typed,
|
typed,
|
||||||
|
/// The user followed a bookmark to get to the page.
|
||||||
bookmark,
|
bookmark,
|
||||||
|
/// Some inner content is loaded. This is true of all images on a
|
||||||
|
/// page, and the contents of the iframe. It is also true of any
|
||||||
|
/// content in a frame if the user did not explicitly follow a link
|
||||||
|
/// to get there.
|
||||||
embed,
|
embed,
|
||||||
|
/// Set when the transition was a permanent redirect.
|
||||||
redirectPermanent,
|
redirectPermanent,
|
||||||
|
/// Set when the transition was a temporary redirect.
|
||||||
redirectTemporary,
|
redirectTemporary,
|
||||||
|
/// Set when the transition is a download.
|
||||||
download,
|
download,
|
||||||
|
/// The user followed a link and got a visit in a frame.
|
||||||
framedLink,
|
framedLink,
|
||||||
|
/// The user reloaded a page.
|
||||||
reload,
|
reload,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,14 +383,37 @@ class Cookie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VisitType {
|
enum VisitType {
|
||||||
|
/// The user followed a link and got a new toplevel window.
|
||||||
link,
|
link,
|
||||||
|
|
||||||
|
/// The user typed the page's URL in the URL bar or selected it from
|
||||||
|
/// URL bar autocomplete results, clicked on it from a history query
|
||||||
|
/// (from the History sidebar, History menu, or history query in the
|
||||||
|
/// personal toolbar or Places organizer.
|
||||||
typed,
|
typed,
|
||||||
|
|
||||||
|
/// The user followed a bookmark to get to the page.
|
||||||
bookmark,
|
bookmark,
|
||||||
|
|
||||||
|
/// Some inner content is loaded. This is true of all images on a
|
||||||
|
/// page, and the contents of the iframe. It is also true of any
|
||||||
|
/// content in a frame if the user did not explicitly follow a link
|
||||||
|
/// to get there.
|
||||||
embed,
|
embed,
|
||||||
|
|
||||||
|
/// Set when the transition was a permanent redirect.
|
||||||
redirectPermanent,
|
redirectPermanent,
|
||||||
|
|
||||||
|
/// Set when the transition was a temporary redirect.
|
||||||
redirectTemporary,
|
redirectTemporary,
|
||||||
|
|
||||||
|
/// Set when the transition is a download.
|
||||||
download,
|
download,
|
||||||
|
|
||||||
|
/// The user followed a link and got a visit in a frame.
|
||||||
framedLink,
|
framedLink,
|
||||||
|
|
||||||
|
/// The user reloaded a page.
|
||||||
reload,
|
reload,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user