basic history working
This commit is contained in:
@@ -28,6 +28,7 @@ part of 'routes.dart';
|
||||
path: 'search/:tabType/:searchText',
|
||||
),
|
||||
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
|
||||
TypedGoRoute<HistoryRoute>(name: 'HistoryRoute', path: 'history'),
|
||||
TypedGoRoute<ContextMenuRoute>(
|
||||
name: 'ContextMenuRoute',
|
||||
path: 'context_menu',
|
||||
@@ -178,3 +179,10 @@ class OpenSharedContentRoute extends GoRouteData with _$OpenSharedContentRoute {
|
||||
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/contextmenu/extensions/hit_result.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/tabs/data/models/container_data.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,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'history',
|
||||
name: 'HistoryRoute',
|
||||
|
||||
factory: _$HistoryRoute._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'context_menu',
|
||||
name: 'ContextMenuRoute',
|
||||
@@ -419,6 +425,26 @@ mixin _$TorProxyRoute on GoRouteData {
|
||||
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 {
|
||||
static ContextMenuRoute _fromState(GoRouterState state) =>
|
||||
ContextMenuRoute(state.extra as String);
|
||||
|
||||
+7
@@ -257,6 +257,13 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
leadingIcon: const Icon(Icons.settings),
|
||||
child: const Text('Settings'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await HistoryRoute().push(context);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.history),
|
||||
child: const Text('History'),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, childRef, child) {
|
||||
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:riverpod/riverpod.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';
|
||||
|
||||
@Riverpod()
|
||||
Future<List<VisitInfo>> browsingHistory(
|
||||
Ref red, {
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
required Set<VisitType> types,
|
||||
}) {
|
||||
final service = GeckoHistoryService();
|
||||
return service.getDetailedVisits(start, end, types);
|
||||
@Riverpod(keepAlive: true)
|
||||
class HistoryFilter extends _$HistoryFilter {
|
||||
void updateVisitType(VisitType type, bool value) {
|
||||
if (value) {
|
||||
state = state.copyWith.visitTypes({...state.visitTypes, type});
|
||||
} else {
|
||||
state = state.copyWith.visitTypes({...state.visitTypes}..remove(type));
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
// **************************************************************************
|
||||
|
||||
String _$browsingHistoryHash() => r'1f644757c60473f00dd94c9d312647baeaec525a';
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
String _$browsingHistoryHash() => r'a684c34fc370474a771c5fcc7982a200de2294e5';
|
||||
|
||||
/// See also [browsingHistory].
|
||||
@ProviderFor(browsingHistory)
|
||||
const browsingHistoryProvider = BrowsingHistoryFamily();
|
||||
|
||||
/// See also [browsingHistory].
|
||||
class BrowsingHistoryFamily extends Family<AsyncValue<List<VisitInfo>>> {
|
||||
/// See also [browsingHistory].
|
||||
const BrowsingHistoryFamily();
|
||||
|
||||
/// See also [browsingHistory].
|
||||
BrowsingHistoryProvider call({
|
||||
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,
|
||||
final browsingHistoryProvider =
|
||||
AutoDisposeFutureProvider<List<VisitInfo>>.internal(
|
||||
browsingHistory,
|
||||
name: r'browsingHistoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$browsingHistoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
}
|
||||
|
||||
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')
|
||||
// ignore: unused_element
|
||||
mixin BrowsingHistoryRef on AutoDisposeFutureProviderRef<List<VisitInfo>> {
|
||||
/// The parameter `start` of this provider.
|
||||
DateTime get start;
|
||||
typedef BrowsingHistoryRef = AutoDisposeFutureProviderRef<List<VisitInfo>>;
|
||||
String _$historyFilterHash() => r'271d60ce79f96ac49d450f01d21cbfdad684ce53';
|
||||
|
||||
/// The parameter `end` of this provider.
|
||||
DateTime get end;
|
||||
|
||||
/// The parameter `types` of this provider.
|
||||
Set<VisitType> get types;
|
||||
}
|
||||
|
||||
class _BrowsingHistoryProviderElement
|
||||
extends AutoDisposeFutureProviderElement<List<VisitInfo>>
|
||||
with BrowsingHistoryRef {
|
||||
_BrowsingHistoryProviderElement(super.provider);
|
||||
|
||||
@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;
|
||||
}
|
||||
/// See also [HistoryFilter].
|
||||
@ProviderFor(HistoryFilter)
|
||||
final historyFilterProvider =
|
||||
NotifierProvider<HistoryFilter, HistoryFilterOptions>.internal(
|
||||
HistoryFilter.new,
|
||||
name: r'historyFilterProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$historyFilterHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$HistoryFilter = Notifier<HistoryFilterOptions>;
|
||||
// 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
|
||||
|
||||
@@ -1,22 +1,204 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.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/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 {
|
||||
const HistoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final history = ref.watch(
|
||||
browsingHistoryProvider(
|
||||
start: DateTime(0),
|
||||
end: DateTime.now(),
|
||||
types: {
|
||||
VisitType.link,
|
||||
VisitType.redirectPermanent,
|
||||
VisitType.redirectTemporary,
|
||||
VisitType.typed,
|
||||
final historyFilter = ref.watch(historyFilterProvider);
|
||||
|
||||
final menuController = useMenuController();
|
||||
|
||||
final historyEntries = ref.watch(browsingHistoryProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
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()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user