finished implementing history with removal options

This commit is contained in:
Fabian Freund
2025-09-19 10:27:38 +02:00
parent 0d7ae20854
commit b01012c76c
17 changed files with 525 additions and 75 deletions
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
class DeleteDataDialog extends HookConsumerWidget {
final Set<DeleteBrowsingDataType> initialSettings;
const DeleteDataDialog({required this.initialSettings});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selections = useState(initialSettings);
return SimpleDialog(
title: const Text('Delete Browsing Data'),
children: [
for (final type in DeleteBrowsingDataType.values)
CheckboxListTile.adaptive(
value: selections.value.contains(type),
controlAffinity: ListTileControlAffinity.leading,
title: Text(type.title),
subtitle: type.description.mapNotNull(
(description) => Text(description),
),
onChanged: (value) {
if (value == true) {
selections.value = {...selections.value, type};
} else {
selections.value = {...selections.value}..remove(type);
}
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: FilledButton.icon(
onPressed: () async {
await ref
.read(browserDataServiceProvider.notifier)
.deleteData(selections.value);
if (context.mounted) {
context.pop();
}
},
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
foregroundColor: Theme.of(context).colorScheme.onError,
),
label: const Text('Delete'),
icon: const Icon(Icons.delete_forever),
),
),
],
);
}
}
@@ -92,6 +92,13 @@ class _BrowserViewState extends ConsumerState<BrowserView>
await ref
.read(browserDataServiceProvider.notifier)
.deleteDataOnEngineStart(settings.deleteBrowsingDataOnQuit);
if (settings.historyAutoCleanInterval > Duration.zero) {
await GeckoHistoryService().deleteVisitsBetween(
DateTime(0),
DateTime.now().subtract(settings.historyAutoCleanInterval),
);
}
});
});
@@ -1,28 +1,20 @@
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.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 DateTimeRange<DateTime>? dateRange;
final Set<VisitType> visitTypes;
HistoryFilterOptions({
required this.start,
required this.end,
required this.visitTypes,
});
HistoryFilterOptions({required this.dateRange, required this.visitTypes});
HistoryFilterOptions.withDefaults()
: this(
start: null,
end: null,
visitTypes: {VisitType.link, VisitType.typed},
);
: this(dateRange: null, visitTypes: {VisitType.link, VisitType.typed});
@override
List<Object?> get hashParameters => [start, end, visitTypes];
List<Object?> get hashParameters => [dateRange, visitTypes];
}
@@ -7,9 +7,7 @@ part of 'history_filter_options.dart';
// **************************************************************************
abstract class _$HistoryFilterOptionsCWProxy {
HistoryFilterOptions start(DateTime? start);
HistoryFilterOptions end(DateTime? end);
HistoryFilterOptions dateRange(DateTimeRange<DateTime>? dateRange);
HistoryFilterOptions visitTypes(Set<VisitType> visitTypes);
@@ -20,8 +18,7 @@ abstract class _$HistoryFilterOptionsCWProxy {
/// HistoryFilterOptions(...).copyWith(id: 12, name: "My name")
/// ````
HistoryFilterOptions call({
DateTime? start,
DateTime? end,
DateTimeRange<DateTime>? dateRange,
Set<VisitType> visitTypes,
});
}
@@ -34,10 +31,8 @@ class _$HistoryFilterOptionsCWProxyImpl
final HistoryFilterOptions _value;
@override
HistoryFilterOptions start(DateTime? start) => this(start: start);
@override
HistoryFilterOptions end(DateTime? end) => this(end: end);
HistoryFilterOptions dateRange(DateTimeRange<DateTime>? dateRange) =>
this(dateRange: dateRange);
@override
HistoryFilterOptions visitTypes(Set<VisitType> visitTypes) =>
@@ -51,19 +46,14 @@ class _$HistoryFilterOptionsCWProxyImpl
/// HistoryFilterOptions(...).copyWith(id: 12, name: "My name")
/// ````
HistoryFilterOptions call({
Object? start = const $CopyWithPlaceholder(),
Object? end = const $CopyWithPlaceholder(),
Object? dateRange = const $CopyWithPlaceholder(),
Object? visitTypes = const $CopyWithPlaceholder(),
}) {
return HistoryFilterOptions(
start: start == const $CopyWithPlaceholder()
? _value.start
dateRange: dateRange == const $CopyWithPlaceholder()
? _value.dateRange
// ignore: cast_nullable_to_non_nullable
: start as DateTime?,
end: end == const $CopyWithPlaceholder()
? _value.end
// ignore: cast_nullable_to_non_nullable
: end as DateTime?,
: dateRange as DateTimeRange<DateTime>?,
visitTypes: visitTypes == const $CopyWithPlaceholder()
? _value.visitTypes
// ignore: cast_nullable_to_non_nullable
@@ -17,6 +17,7 @@
* 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:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -34,6 +35,10 @@ class HistoryFilter extends _$HistoryFilter {
}
}
void setDateRange(DateTimeRange<DateTime>? range) {
state = state.copyWith.dateRange(range);
}
@override
HistoryFilterOptions build() {
return HistoryFilterOptions.withDefaults();
@@ -47,8 +52,8 @@ Future<List<VisitInfo>> browsingHistory(Ref ref) {
final service = GeckoHistoryService();
return service
.getDetailedVisits(
options.start ?? DateTime(0),
options.end ?? DateTime(9999),
options.dateRange?.start ?? DateTime(0),
options.dateRange?.end ?? DateTime(9999),
options.visitTypes,
)
.then(
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$browsingHistoryHash() => r'a684c34fc370474a771c5fcc7982a200de2294e5';
String _$browsingHistoryHash() => r'6447fd4d8209c3befb07f2d82fa41a24bd3accb1';
/// See also [browsingHistory].
@ProviderFor(browsingHistory)
@@ -24,7 +24,7 @@ final browsingHistoryProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef BrowsingHistoryRef = AutoDisposeFutureProviderRef<List<VisitInfo>>;
String _$historyFilterHash() => r'271d60ce79f96ac49d450f01d21cbfdad684ce53';
String _$historyFilterHash() => r'68a604fe857476f7c15be89352a7b10bc456c3d6';
/// See also [HistoryFilter].
@ProviderFor(HistoryFilter)
@@ -2,27 +2,34 @@ 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_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:go_router/go_router.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/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/delete_data.dart';
import 'package:weblibre/features/geckoview/features/history/domain/providers.dart';
import 'package:weblibre/features/user/data/models/general_settings.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');
static final _datePattern = DateFormat.MMMd().addPattern('Hm');
Section({
Key? key,
super.key,
required BuildContext context,
required String title,
required List<VisitInfo> items,
required Set<VisitInfo> selectedItems,
required void Function(VisitInfo) onTap,
required void Function(VisitInfo) onLongPress,
}) : super(
key: key,
pushPinnedChildren: true,
children: [
SliverPinnedHeader(
@@ -50,16 +57,40 @@ class Section extends MultiSliver {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
leading: UrlIcon([Uri.parse(item.url)], iconSize: 24),
leading: selectedItems.contains(item)
? const CircleAvatar(
radius: 12,
child: Icon(Icons.check, size: 12),
)
: UrlIcon([Uri.parse(item.url)], iconSize: 24),
title: item.title.mapNotNull((title) => Text(title)),
subtitle: Text(
item.url,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: Consumer(
builder: (context, ref, _) {
return IconButton(
onPressed: () async {
final service = GeckoHistoryService();
await service.deleteVisit(item);
// ignore: unused_result
await ref.refresh(browsingHistoryProvider.future);
},
icon: const Icon(MdiIcons.closeCircle),
);
},
),
onTap: () {
onTap(item);
},
onLongPress: () {
onLongPress(item);
},
),
Padding(
padding: const EdgeInsets.only(left: 54),
padding: const EdgeInsets.only(left: 54, right: 16),
child: Wrap(
spacing: 8.0,
children: [
@@ -113,43 +144,126 @@ class HistoryScreen extends HookConsumerWidget {
final historyEntries = ref.watch(browsingHistoryProvider);
final selectedItems = useState(<VisitInfo>{});
return Scaffold(
appBar: AppBar(
title: selectedItems.value.isEmpty
? const Text('History')
: Text('${selectedItems.value.length} selected'),
actions: [
if (selectedItems.value.isNotEmpty)
IconButton(
onPressed: () async {
for (final item in selectedItems.value) {
final service = GeckoHistoryService();
await service.deleteVisit(item);
}
selectedItems.value = {};
// ignore: unused_result
await ref.refresh(browsingHistoryProvider.future);
},
icon: const Icon(Icons.delete),
)
else
IconButton(
onPressed: () async {
await showDialog(
context: context,
builder: (context) {
return const DeleteDataDialog(
initialSettings: {DeleteBrowsingDataType.history},
);
},
);
// ignore: unused_result
await ref.refresh(browsingHistoryProvider.future);
},
icon: const Icon(Icons.delete),
),
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(),
},
),
MenuItemButton(
leadingIcon: const Icon(MdiIcons.calendarRange),
trailingIcon: historyFilter.dateRange.mapNotNull(
(_) => IconButton(
onPressed: () {
ref
.read(historyFilterProvider.notifier)
.setDateRange(null);
},
icon: const Icon(Icons.clear),
),
),
child:
historyFilter.dateRange.mapNotNull(
(range) => Text(
'${DateFormat.yMd().format(range.start)} - ${DateFormat.yMd().format(range.end)}',
),
) ??
const Text('Filter Date'),
onPressed: () async {
final range = await showDateRangePicker(
context: context,
initialDateRange: historyFilter.dateRange,
firstDate: DateTime.now().subtract(
const Duration(days: 365),
),
lastDate: DateTime.now(),
);
ref
.read(historyFilterProvider.notifier)
.setDateRange(
range.mapNotNull(
(range) => DateTimeRange(
start: range.start,
//Make sure to include last day fully
end: range.end.add(
const Duration(days: 1) -
const Duration(milliseconds: 1),
),
),
),
);
},
),
const Divider(),
...{
VisitType.link,
VisitType.typed,
VisitType.reload,
VisitType.download,
}.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: () {
@@ -159,7 +273,7 @@ class HistoryScreen extends HookConsumerWidget {
menuController.open();
}
},
icon: const Icon(Icons.more_vert),
icon: const Icon(MdiIcons.filter),
),
),
],
@@ -182,10 +296,41 @@ class HistoryScreen extends HookConsumerWidget {
[EquatableValue(data)],
);
void toggleSelected(VisitInfo item) {
if (selectedItems.value.contains(item)) {
selectedItems.value = {...selectedItems.value}
..remove(item);
} else {
selectedItems.value = {...selectedItems.value, item};
}
}
return CustomScrollView(
slivers: [
for (final MapEntry(:key, :value) in groups.entries)
Section(context: context, title: key, items: value),
Section(
context: context,
title: key,
items: value,
selectedItems: selectedItems.value,
onLongPress: toggleSelected,
onTap: (item) async {
if (selectedItems.value.isNotEmpty) {
toggleSelected(item);
} else {
await ref
.read(tabRepositoryProvider.notifier)
.addTab(
url: Uri.parse(item.url),
private: false,
);
if (context.mounted) {
context.pop();
}
}
},
),
],
);
},
@@ -24,6 +24,7 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/delete_data.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
@@ -49,7 +50,7 @@ class WebEngineSettingsScreen extends HookConsumerWidget {
SwitchListTile.adaptive(
title: const Text('Incognito Mode'),
subtitle: const Text(
'Deletes all browsing data upon app restart for enhanced privacy.',
'Deletes selected browsing data upon app restart for enhanced privacy.',
),
secondary: const Icon(MdiIcons.incognito),
value: generalSettings.deleteBrowsingDataOnQuit != null,
@@ -114,6 +115,94 @@ class WebEngineSettingsScreen extends HookConsumerWidget {
],
),
),
ListTile(
title: const Text('Delete Browsing Data'),
contentPadding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
leading: const Icon(MdiIcons.delete),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
await showDialog(
context: context,
builder: (context) {
return const DeleteDataDialog(initialSettings: {});
},
);
},
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Auto-Clear History'),
subtitle: Text(
'Automatically delete browsing history older than the selected time period',
),
leading: Icon(MdiIcons.deleteClock),
contentPadding: EdgeInsets.zero,
),
Padding(
padding: const EdgeInsets.only(left: 40.0),
child: DropdownMenu(
initialSelection:
generalSettings.historyAutoCleanInterval,
inputDecorationTheme: InputDecorationTheme(
prefixIconConstraints: BoxConstraints.tight(
const Size.square(24),
),
),
width: double.infinity,
dropdownMenuEntries: const [
DropdownMenuEntry(
value: Duration.zero,
label: 'Never',
),
DropdownMenuEntry(
value: Duration(days: 1),
label: '1 Day',
),
DropdownMenuEntry(
value: Duration(days: 7),
label: '1 Week',
),
DropdownMenuEntry(
value: Duration(days: 14),
label: '2 Weeks',
),
DropdownMenuEntry(
value: Duration(days: 30),
label: '1 Month',
),
DropdownMenuEntry(
value: Duration(days: 90),
label: '3 Months',
),
],
onSelected: (value) async {
await ref
.read(
saveGeneralSettingsControllerProvider.notifier,
)
.save(
(currentSettings) => currentSettings.copyWith
.historyAutoCleanInterval(
value ?? Duration.zero,
),
);
},
),
),
],
),
),
SwitchListTile.adaptive(
title: const Text('Global Privacy Control (GPC)'),
secondary: const Icon(MdiIcons.incognitoCircleOff),
@@ -63,6 +63,7 @@ class GeneralSettings with FastEquatable {
final TabIntentOpenSetting tabIntentOpenSetting;
final bool autoHideTabBar;
final TabBarSwipeAction tabBarSwipeAction;
final Duration historyAutoCleanInterval;
GeneralSettings({
required this.themeMode,
@@ -78,6 +79,7 @@ class GeneralSettings with FastEquatable {
required this.tabIntentOpenSetting,
required this.autoHideTabBar,
required this.tabBarSwipeAction,
required this.historyAutoCleanInterval,
});
GeneralSettings.withDefaults({
@@ -94,6 +96,7 @@ class GeneralSettings with FastEquatable {
TabIntentOpenSetting? tabIntentOpenSetting,
bool? autoHideTabBar,
TabBarSwipeAction? tabBarSwipeAction,
Duration? historyAutoCleanInterval,
}) : themeMode = themeMode ?? ThemeMode.dark,
enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false,
@@ -107,7 +110,9 @@ class GeneralSettings with FastEquatable {
tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask,
autoHideTabBar = autoHideTabBar ?? true,
tabBarSwipeAction =
tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened;
tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened,
historyAutoCleanInterval =
historyAutoCleanInterval ?? const Duration(days: 90);
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
_$GeneralSettingsFromJson(json);
@@ -129,5 +134,6 @@ class GeneralSettings with FastEquatable {
tabIntentOpenSetting,
autoHideTabBar,
tabBarSwipeAction,
historyAutoCleanInterval,
];
}
@@ -39,6 +39,8 @@ abstract class _$GeneralSettingsCWProxy {
GeneralSettings tabBarSwipeAction(TabBarSwipeAction tabBarSwipeAction);
GeneralSettings historyAutoCleanInterval(Duration historyAutoCleanInterval);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
@@ -59,6 +61,7 @@ abstract class _$GeneralSettingsCWProxy {
TabIntentOpenSetting tabIntentOpenSetting,
bool autoHideTabBar,
TabBarSwipeAction tabBarSwipeAction,
Duration historyAutoCleanInterval,
});
}
@@ -122,6 +125,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
GeneralSettings tabBarSwipeAction(TabBarSwipeAction tabBarSwipeAction) =>
this(tabBarSwipeAction: tabBarSwipeAction);
@override
GeneralSettings historyAutoCleanInterval(Duration historyAutoCleanInterval) =>
this(historyAutoCleanInterval: historyAutoCleanInterval);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
@@ -143,6 +150,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? tabIntentOpenSetting = const $CopyWithPlaceholder(),
Object? autoHideTabBar = const $CopyWithPlaceholder(),
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
Object? historyAutoCleanInterval = const $CopyWithPlaceholder(),
}) {
return GeneralSettings(
themeMode: themeMode == const $CopyWithPlaceholder()
@@ -203,6 +211,11 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.tabBarSwipeAction
// ignore: cast_nullable_to_non_nullable
: tabBarSwipeAction as TabBarSwipeAction,
historyAutoCleanInterval:
historyAutoCleanInterval == const $CopyWithPlaceholder()
? _value.historyAutoCleanInterval
// ignore: cast_nullable_to_non_nullable
: historyAutoCleanInterval as Duration,
);
}
}
@@ -247,6 +260,11 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
_$TabBarSwipeActionEnumMap,
json['tabBarSwipeAction'],
),
historyAutoCleanInterval: json['historyAutoCleanInterval'] == null
? null
: Duration(
microseconds: (json['historyAutoCleanInterval'] as num).toInt(),
),
);
Map<String, dynamic> _$GeneralSettingsToJson(
@@ -270,6 +288,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
_$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!,
'autoHideTabBar': instance.autoHideTabBar,
'tabBarSwipeAction': _$TabBarSwipeActionEnumMap[instance.tabBarSwipeAction]!,
'historyAutoCleanInterval': instance.historyAutoCleanInterval.inMicroseconds,
};
const _$ThemeModeEnumMap = {
@@ -96,6 +96,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.string,
db.typeMapping,
),
'historyAutoCleanInterval': settings['historyAutoCleanInterval']?.readAs(
DriftSqlType.int,
db.typeMapping,
),
});
}
@@ -27,7 +27,7 @@ final generalSettingsWithDefaultsProvider =
typedef GeneralSettingsWithDefaultsRef =
AutoDisposeProviderRef<GeneralSettings>;
String _$generalSettingsRepositoryHash() =>
r'0d207ae4ae9fd94b1257eb8aa4cfd73d4a47caf2';
r'3e2a07b8956094cd9376a254d9aedaa80a3c45c4';
/// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository)
@@ -69,4 +69,32 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
}
}
}
override fun deleteVisit(
url: String,
timestamp: Long,
callback: (Result<Unit>) -> Unit
) {
coroutineScope.launch {
withContext(Dispatchers.Main) {
components.core.historyStorage.deleteVisit(url, timestamp);
callback(Result.success(Unit))
}
}
}
override fun deleteVisitsBetween(
startMillis: Long,
endMillis: Long,
callback: (Result<Unit>) -> Unit
) {
coroutineScope.launch {
withContext(Dispatchers.Main) {
components.core.historyStorage.deleteVisitsBetween(startMillis, endMillis);
callback(Result.success(Unit))
}
}
}
}
@@ -4750,6 +4750,8 @@ interface GeckoDeleteBrowsingDataController {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface GeckoHistoryApi {
fun getDetailedVisits(startMillis: Long, endMillis: Long, excludeTypes: List<VisitType>, callback: (Result<List<VisitInfo>>) -> Unit)
fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit)
fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit)
companion object {
/** The codec used by GeckoHistoryApi. */
@@ -4782,6 +4784,46 @@ interface GeckoHistoryApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisit$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val urlArg = args[0] as String
val timestampArg = args[1] as Long
api.deleteVisit(urlArg, timestampArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
reply.reply(GeckoPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startMillisArg = args[0] as Long
val endMillisArg = args[1] as Long
api.deleteVisitsBetween(startMillisArg, endMillisArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
reply.reply(GeckoPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -24,4 +24,15 @@ class GeckoHistoryService {
VisitType.values.toSet().difference(types).toList(),
);
}
Future<void> deleteVisit(VisitInfo info) {
return _api.deleteVisit(info.url, info.visitTime);
}
Future<void> deleteVisitsBetween(DateTime start, DateTime end) {
return _api.deleteVisitsBetween(
start.millisecondsSinceEpoch,
end.millisecondsSinceEpoch,
);
}
}
@@ -5731,6 +5731,52 @@ class GeckoHistoryApi {
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<VisitInfo>();
}
}
Future<void> deleteVisit(String url, int timestamp) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisit$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url, timestamp]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
Future<void> deleteVisitsBetween(int startMillis, int endMillis) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[startMillis, endMillis]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
class GeckoDownloadsApi {
@@ -1248,6 +1248,12 @@ abstract class GeckoHistoryApi {
int endMillis,
List<VisitType> excludeTypes,
);
@async
void deleteVisit(String url, int timestamp);
@async
void deleteVisitsBetween(int startMillis, int endMillis);
}
@HostApi()