diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 6ed9ec3..77d04ee 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -6,7 +6,7 @@ /// Locales: 2 /// Strings: 234 (117 per locale) /// -/// Built on 2024-02-25 at 12:46 UTC +/// Built on 2024-02-25 at 16:44 UTC // coverage:ignore-file // ignore_for_file: type=lint diff --git a/lib/providers/app_status.provider.dart b/lib/providers/app_status.provider.dart index 05c45c7..06f7688 100644 --- a/lib/providers/app_status.provider.dart +++ b/lib/providers/app_status.provider.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:linkdy/constants/shared_preferences_keys.dart'; +import 'package:linkdy/screens/bookmarks/provider/favicon_loader.provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/providers/shared_preferences.provider.dart'; @@ -54,6 +55,7 @@ class AppStatus extends _$AppStatus { void setShowFavicon(bool value) { state.showFavicon = value; ref.read(sharedPreferencesProvider).setBool(SharedPreferencesKeys.showFavicon, value); + ref.read(faviconStoreProvider.notifier).clearFavicons(); ref.notifyListeners(); } } diff --git a/lib/providers/app_status.provider.g.dart b/lib/providers/app_status.provider.g.dart index abf6347..732f9db 100644 --- a/lib/providers/app_status.provider.g.dart +++ b/lib/providers/app_status.provider.g.dart @@ -21,7 +21,7 @@ final selectedThemeProvider = AutoDisposeProvider.internal( ); typedef SelectedThemeRef = AutoDisposeProviderRef; -String _$appStatusHash() => r'3b87acee218adcc5707acfb30fd6e5c650b410ce'; +String _$appStatusHash() => r'f9f4e14943668e49749dfb7e65b42add404e9e8e'; /// See also [AppStatus]. @ProviderFor(AppStatus) diff --git a/lib/screens/bookmarks/model/favicon_loader.model.dart b/lib/screens/bookmarks/model/favicon_loader.model.dart index fe89452..a70753f 100644 --- a/lib/screens/bookmarks/model/favicon_loader.model.dart +++ b/lib/screens/bookmarks/model/favicon_loader.model.dart @@ -2,8 +2,10 @@ import 'package:linkdy/models/favicon_item.dart'; class FaviconLoaderModel { List favicons; + bool loadingFavicons; FaviconLoaderModel({ required this.favicons, + this.loadingFavicons = true, }); } diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.dart b/lib/screens/bookmarks/provider/bookmarks.provider.dart index c53dd44..ef1b84a 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.dart @@ -1,5 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:linkdy/screens/bookmarks/provider/favicon_loader.provider.dart'; import 'package:linkdy/screens/bookmarks/model/bookmarks.model.dart'; import 'package:linkdy/providers/api_client.provider.dart'; @@ -15,6 +16,7 @@ FutureOr bookmarksRequest(BookmarksRequestRef ref, int limit) async { ); if (result.successful == true) { + ref.read(faviconStoreProvider.notifier).loadFavicons(result.content!.results!); ref.read(bookmarksProvider).bookmarks = result.content!.results!; ref.read(bookmarksProvider).maxNumber = result.content!.count!; ref.read(bookmarksProvider).currentPage = 0; @@ -38,6 +40,7 @@ FutureOr bookmarksRequestLoadMore(BookmarksRequestLoadMoreRef ref) async { ); if (result.successful == true) { + ref.read(faviconStoreProvider.notifier).loadFavicons(result.content!.results!); provider.bookmarks = [...provider.bookmarks, ...result.content!.results!]; provider.maxNumber = result.content!.count!; provider.currentPage = provider.currentPage + 1; diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart index 55ab3a1..2e3c422 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart @@ -6,7 +6,7 @@ part of 'bookmarks.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$bookmarksRequestHash() => r'04ee313bc986046f5c401f7539b0ba2386001303'; +String _$bookmarksRequestHash() => r'2da88ec52b4f959e6355f5f5fa8660d6cebd157d'; /// Copied from Dart SDK class _SystemHash { @@ -156,7 +156,7 @@ class _BookmarksRequestProviderElement } String _$bookmarksRequestLoadMoreHash() => - r'523edffa3f18851e1879948e7f8996904aa0aa32'; + r'36b0703684218e1c6bf01643788d5f9d3da32327'; /// See also [bookmarksRequestLoadMore]. @ProviderFor(bookmarksRequestLoadMore) diff --git a/lib/screens/bookmarks/provider/favicon_loader.provider.dart b/lib/screens/bookmarks/provider/favicon_loader.provider.dart index 2bd898a..272f58f 100644 --- a/lib/screens/bookmarks/provider/favicon_loader.provider.dart +++ b/lib/screens/bookmarks/provider/favicon_loader.provider.dart @@ -1,40 +1,39 @@ -import 'dart:convert'; - -import 'package:http/http.dart' as http; +import 'package:flutter/foundation.dart'; import 'package:favicon/favicon.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/screens/bookmarks/model/favicon_loader.model.dart'; import 'package:linkdy/models/favicon_item.dart'; +import 'package:linkdy/models/data/bookmarks.dart'; part 'favicon_loader.provider.g.dart'; -@riverpod -FutureOr fetchFavicon(FetchFaviconRef ref, String bookmarkUrl) async { - final exists = ref.watch(faviconStoreProvider).favicons.where((favicon) => favicon.url == bookmarkUrl).toList(); - if (exists.isNotEmpty) { - return exists[0]; - } +// @riverpod +// FutureOr fetchFavicon(FetchFaviconRef ref, String bookmarkUrl) async { +// final exists = ref.watch(faviconStoreProvider).favicons.where((favicon) => favicon.url == bookmarkUrl).toList(); +// if (exists.isNotEmpty) { +// return exists[0]; +// } - final faviconResult = await FaviconFinder.getBest(bookmarkUrl); - if (faviconResult == null) { - return null; - } +// final faviconResult = await FaviconFinder.getBest(bookmarkUrl); +// if (faviconResult == null) { +// return null; +// } - final result = await http.get(Uri.parse(faviconResult.url)); - if (result.statusCode == 200) { - final faviconItem = FaviconItem( - url: bookmarkUrl, - favicon: faviconResult.url.contains("svg") ? result.body : base64.encode(result.bodyBytes), - isSvg: faviconResult.url.contains("svg"), - ); - ref.read(faviconStoreProvider).favicons.add(faviconItem); - return faviconItem; - } else { - return null; - } -} +// final result = await http.get(Uri.parse(faviconResult.url)); +// if (result.statusCode == 200) { +// final faviconItem = FaviconItem( +// url: bookmarkUrl, +// favicon: faviconResult.url.contains("svg") ? result.body : base64.encode(result.bodyBytes), +// isSvg: faviconResult.url.contains("svg"), +// ); +// ref.read(faviconStoreProvider).favicons.add(faviconItem); +// return faviconItem; +// } else { +// return null; +// } +// } @Riverpod(keepAlive: true) class FaviconStore extends _$FaviconStore { @@ -44,4 +43,33 @@ class FaviconStore extends _$FaviconStore { favicons: [], ); } + + void clearFavicons() { + state.favicons = []; + } + + void loadFavicons(List bookmarks) async { + state.loadingFavicons = true; + ref.notifyListeners(); + + final mappedSaved = state.favicons.map((e) => e.url); + final notExist = bookmarks.map((b) => b.url!).where((b) => !mappedSaved.contains(b)).toList(); + + final favicons = await compute( + (message) => Future.wait(message.map((e) => FaviconFinder.getBest(e))), + notExist, + ); + + final faviconUrls = favicons.asMap().entries.map( + (e) => FaviconItem( + url: notExist[e.key], + favicon: e.value!.url, + isSvg: e.value!.url.contains('svg'), + ), + ); + + state.favicons = [...state.favicons, ...faviconUrls]; + state.loadingFavicons = false; + ref.notifyListeners(); + } } diff --git a/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart b/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart index 0605ed9..ef5bbef 100644 --- a/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart +++ b/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart @@ -6,157 +6,7 @@ part of 'favicon_loader.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$fetchFaviconHash() => r'90d9f0060aa68162174a1d76a6faeee5ae820494'; - -/// 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 [fetchFavicon]. -@ProviderFor(fetchFavicon) -const fetchFaviconProvider = FetchFaviconFamily(); - -/// See also [fetchFavicon]. -class FetchFaviconFamily extends Family> { - /// See also [fetchFavicon]. - const FetchFaviconFamily(); - - /// See also [fetchFavicon]. - FetchFaviconProvider call( - String bookmarkUrl, - ) { - return FetchFaviconProvider( - bookmarkUrl, - ); - } - - @override - FetchFaviconProvider getProviderOverride( - covariant FetchFaviconProvider provider, - ) { - return call( - provider.bookmarkUrl, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'fetchFaviconProvider'; -} - -/// See also [fetchFavicon]. -class FetchFaviconProvider extends AutoDisposeFutureProvider { - /// See also [fetchFavicon]. - FetchFaviconProvider( - String bookmarkUrl, - ) : this._internal( - (ref) => fetchFavicon( - ref as FetchFaviconRef, - bookmarkUrl, - ), - from: fetchFaviconProvider, - name: r'fetchFaviconProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$fetchFaviconHash, - dependencies: FetchFaviconFamily._dependencies, - allTransitiveDependencies: - FetchFaviconFamily._allTransitiveDependencies, - bookmarkUrl: bookmarkUrl, - ); - - FetchFaviconProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.bookmarkUrl, - }) : super.internal(); - - final String bookmarkUrl; - - @override - Override overrideWith( - FutureOr Function(FetchFaviconRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: FetchFaviconProvider._internal( - (ref) => create(ref as FetchFaviconRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - bookmarkUrl: bookmarkUrl, - ), - ); - } - - @override - AutoDisposeFutureProviderElement createElement() { - return _FetchFaviconProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is FetchFaviconProvider && other.bookmarkUrl == bookmarkUrl; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, bookmarkUrl.hashCode); - - return _SystemHash.finish(hash); - } -} - -mixin FetchFaviconRef on AutoDisposeFutureProviderRef { - /// The parameter `bookmarkUrl` of this provider. - String get bookmarkUrl; -} - -class _FetchFaviconProviderElement - extends AutoDisposeFutureProviderElement - with FetchFaviconRef { - _FetchFaviconProviderElement(super.provider); - - @override - String get bookmarkUrl => (origin as FetchFaviconProvider).bookmarkUrl; -} - -String _$faviconStoreHash() => r'a082b856f1564d04dfd9209b7c2efaff6f146e68'; +String _$faviconStoreHash() => r'87cd0d0c0df889ca3238b6c05389b8dd351d189c'; /// See also [FaviconStore]. @ProviderFor(FaviconStore) diff --git a/lib/screens/bookmarks/ui/bookmark_item.dart b/lib/screens/bookmarks/ui/bookmark_item.dart index fad760e..86a1c90 100644 --- a/lib/screens/bookmarks/ui/bookmark_item.dart +++ b/lib/screens/bookmarks/ui/bookmark_item.dart @@ -46,44 +46,76 @@ class BookmarkItem extends ConsumerWidget { Row( children: [ if (ref.watch(appStatusProvider).showFavicon == true) - FutureBuilder( - future: ref.read(fetchFaviconProvider(bookmark.url!).future), - builder: (context, snapshot) { - if (snapshot.hasData == false) { - return Padding( - padding: const EdgeInsets.only(right: 8), - child: ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Skeletonizer( - enabled: true, - ignoreContainers: true, - child: Container( - width: 16, - height: 16, - color: Colors.black, + Padding( + padding: const EdgeInsets.only(right: 8), + child: ref.watch(faviconStoreProvider).loadingFavicons == true + ? Padding( + padding: const EdgeInsets.only(right: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Skeletonizer( + enabled: true, + ignoreContainers: true, + child: Container( + width: 16, + height: 16, + color: Colors.black, + ), ), ), + ) + : Builder( + builder: (context) { + final faviconItem = ref + .watch(faviconStoreProvider) + .favicons + .where((f) => f.url == bookmark.url!) + .toList(); + if (faviconItem.isEmpty) return const SizedBox(); + if (faviconItem[0].isSvg == true) { + return SvgPicture.network( + faviconItem[0].favicon, + width: 16, + height: 16, + placeholderBuilder: (context) => ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Skeletonizer( + enabled: true, + ignoreContainers: true, + child: Container( + width: 16, + height: 16, + color: Colors.black, + ), + ), + ), + ); + } else { + return Image.network( + faviconItem[0].favicon, + width: 16, + height: 16, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress != null) { + return ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Skeletonizer( + enabled: true, + ignoreContainers: true, + child: Container( + width: 16, + height: 16, + color: Colors.black, + ), + ), + ); + } + return child; + }, + ); + } + }, ), - ); - } - return Row( - children: [ - if (snapshot.data!.isSvg == true) - SvgPicture.string( - snapshot.data!.favicon, - width: 16, - height: 16, - ), - if (snapshot.data!.isSvg == false) - Image.memory( - base64Decode(snapshot.data!.favicon), - width: 16, - height: 16, - ), - const SizedBox(width: 8), - ], - ); - }, ), Expanded( child: Text(