diff --git a/app/lib/data/database/converters/uri.dart b/app/lib/data/database/converters/uri.dart index 48d372c6..92a47647 100644 --- a/app/lib/data/database/converters/uri.dart +++ b/app/lib/data/database/converters/uri.dart @@ -25,7 +25,8 @@ class UriConverter extends TypeConverter { @override Uri fromSql(String fromDb) { - return uri_parser.tryParseUrl(fromDb, eagerParsing: true)!; + return uri_parser.tryParseUrl(fromDb, eagerParsing: true) ?? + Uri.parse(fromDb); } @override diff --git a/app/lib/features/geckoview/features/search/presentation/dialogs/edit_top_site_dialog.dart b/app/lib/features/geckoview/features/search/presentation/dialogs/edit_top_site_dialog.dart index 385cd9b4..3a4a56ae 100644 --- a/app/lib/features/geckoview/features/search/presentation/dialogs/edit_top_site_dialog.dart +++ b/app/lib/features/geckoview/features/search/presentation/dialogs/edit_top_site_dialog.dart @@ -18,6 +18,7 @@ * along with this program. If not, see . */ import 'package:flutter/material.dart'; +import 'package:weblibre/utils/uri_parser.dart' as uri_parser; Future<({String title, Uri url})?> showEditTopSiteDialog( BuildContext context, { @@ -94,8 +95,11 @@ class _EditTopSiteDialogState extends State<_EditTopSiteDialog> { if (value == null || value.trim().isEmpty) { return 'URL cannot be empty'; } - final uri = Uri.tryParse(value.trim()); - if (uri == null || !uri.hasScheme || uri.host.isEmpty) { + final parsed = uri_parser.tryParseUrl( + value.trim(), + eagerParsing: true, + ); + if (parsed == null) { return 'Enter a valid URL'; } return null; @@ -112,7 +116,11 @@ class _EditTopSiteDialogState extends State<_EditTopSiteDialog> { TextButton( onPressed: () { if (_formKey.currentState?.validate() == true) { - final url = Uri.parse(_urlController.text.trim()); + final url = uri_parser.tryParseUrl( + _urlController.text.trim(), + eagerParsing: true, + ); + if (url == null) return; Navigator.pop( context, (title: _titleController.text.trim(), url: url), diff --git a/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.dart b/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.dart index 9b9f6d99..3e6360bf 100644 --- a/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.dart +++ b/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.dart @@ -30,6 +30,7 @@ import 'package:weblibre/features/geckoview/features/top_sites/data/providers.da import 'package:weblibre/features/geckoview/features/top_sites/domain/entities/top_site_item.dart'; import 'package:weblibre/features/geckoview/features/top_sites/domain/entities/top_site_source.dart'; import 'package:weblibre/features/geckoview/features/top_sites/domain/providers.dart'; +import 'package:weblibre/utils/uri_parser.dart' as uri_parser; part 'top_site_repository.g.dart'; @@ -81,9 +82,7 @@ class TopSiteRepository extends _$TopSiteRepository { final remaining = targetCount - persisted.length; final historyItems = await _getHistoryItems( limit: remaining, - excludeUrls: persisted - .map((s) => s.url.normalized.toString()) - .toSet(), + excludeUrls: persisted.map((s) => s.url.normalized.toString()).toSet(), ); return [...persisted, ...historyItems]; @@ -126,10 +125,23 @@ class TopSiteRepository extends _$TopSiteRepository { ); } + static Uri _validateUrl(Uri url) { + final normalized = url.normalized; + final parsed = uri_parser.tryParseUrl( + normalized.toString(), + eagerParsing: true, + ); + if (parsed == null) { + throw ArgumentError.value(url.toString(), 'url', 'Invalid URL'); + } + return normalized; + } + Future addPinnedSite({ required String title, required Uri url, }) async { + _validateUrl(url); final db = ref.read(topSiteDatabaseProvider); // Check if URL already exists as a persisted site @@ -163,6 +175,7 @@ class TopSiteRepository extends _$TopSiteRepository { required String title, required Uri url, }) { + _validateUrl(url); return ref .read(topSiteDatabaseProvider) .topSiteDao