diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 07c6cb1..6ed9ec3 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 03:56 UTC +/// Built on 2024-02-25 at 12:46 UTC // coverage:ignore-file // ignore_for_file: type=lint diff --git a/lib/main.dart b/lib/main.dart index 6dba66d..c3bd858 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:linkdy/constants/global_keys.dart'; +import 'package:linkdy/providers/temporary_directory.provider.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -35,12 +37,15 @@ void main() async { final appInfo = await PackageInfo.fromPlatform(); + final baseDir = await getTemporaryDirectory(); + void startApp() => runApp( TranslationProvider( child: ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(sharedPreferences), appInfoProvider.overrideWithValue(appInfo), + temporaryDirectoryProvider.overrideWithValue(baseDir), ], child: const MyApp(), ), diff --git a/lib/models/favicon_item.dart b/lib/models/favicon_item.dart new file mode 100644 index 0000000..12b2c20 --- /dev/null +++ b/lib/models/favicon_item.dart @@ -0,0 +1,11 @@ +class FaviconItem { + final String url; + final String favicon; + final bool isSvg; + + const FaviconItem({ + required this.url, + required this.favicon, + required this.isSvg, + }); +} diff --git a/lib/providers/temporary_directory.provider.dart b/lib/providers/temporary_directory.provider.dart new file mode 100644 index 0000000..49dde6b --- /dev/null +++ b/lib/providers/temporary_directory.provider.dart @@ -0,0 +1,10 @@ +import 'dart:io'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'temporary_directory.provider.g.dart'; + +@Riverpod(keepAlive: true) +Directory temporaryDirectory(TemporaryDirectoryRef ref) { + throw UnimplementedError(); +} diff --git a/lib/providers/temporary_directory.provider.g.dart b/lib/providers/temporary_directory.provider.g.dart new file mode 100644 index 0000000..372c06e --- /dev/null +++ b/lib/providers/temporary_directory.provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'temporary_directory.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$temporaryDirectoryHash() => + r'7d9abadfdaac781835728fd72f6672186d5410d3'; + +/// See also [temporaryDirectory]. +@ProviderFor(temporaryDirectory) +final temporaryDirectoryProvider = Provider.internal( + temporaryDirectory, + name: r'temporaryDirectoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$temporaryDirectoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef TemporaryDirectoryRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/screens/bookmarks/model/favicon_loader.model.dart b/lib/screens/bookmarks/model/favicon_loader.model.dart new file mode 100644 index 0000000..fe89452 --- /dev/null +++ b/lib/screens/bookmarks/model/favicon_loader.model.dart @@ -0,0 +1,9 @@ +import 'package:linkdy/models/favicon_item.dart'; + +class FaviconLoaderModel { + List favicons; + + FaviconLoaderModel({ + required this.favicons, + }); +} diff --git a/lib/screens/bookmarks/provider/favicon_loader.provider.dart b/lib/screens/bookmarks/provider/favicon_loader.provider.dart new file mode 100644 index 0000000..2bd898a --- /dev/null +++ b/lib/screens/bookmarks/provider/favicon_loader.provider.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +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'; + +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]; + } + + 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; + } +} + +@Riverpod(keepAlive: true) +class FaviconStore extends _$FaviconStore { + @override + FaviconLoaderModel build() { + return FaviconLoaderModel( + favicons: [], + ); + } +} diff --git a/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart b/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart new file mode 100644 index 0000000..0605ed9 --- /dev/null +++ b/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +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'; + +/// See also [FaviconStore]. +@ProviderFor(FaviconStore) +final faviconStoreProvider = + NotifierProvider.internal( + FaviconStore.new, + name: r'faviconStoreProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$faviconStoreHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$FaviconStore = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/screens/bookmarks/ui/bookmark_item.dart b/lib/screens/bookmarks/ui/bookmark_item.dart index d8366ef..6bc75eb 100644 --- a/lib/screens/bookmarks/ui/bookmark_item.dart +++ b/lib/screens/bookmarks/ui/bookmark_item.dart @@ -1,9 +1,12 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; -import 'package:favicon/favicon.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:linkdy/screens/bookmarks/provider/favicon_loader.provider.dart'; + import 'package:linkdy/models/data/bookmarks.dart'; import 'package:linkdy/providers/app_status_provider.dart'; import 'package:linkdy/providers/router_provider.dart'; @@ -44,7 +47,7 @@ class BookmarkItem extends ConsumerWidget { children: [ if (ref.watch(appStatusProvider).showFavicon == true) FutureBuilder( - future: FaviconFinder.getBest(bookmark.url!), + future: ref.read(fetchFaviconProvider(bookmark.url!).future), builder: (context, snapshot) { if (snapshot.hasData == false) { return Padding( @@ -65,15 +68,15 @@ class BookmarkItem extends ConsumerWidget { } return Row( children: [ - if (snapshot.data!.url.contains("svg")) - SvgPicture.network( - snapshot.data!.url, + if (snapshot.data!.isSvg == true) + SvgPicture.string( + snapshot.data!.favicon, width: 16, height: 16, ), - if (!snapshot.data!.url.contains("svg")) - Image.network( - snapshot.data!.url, + if (snapshot.data!.isSvg == false) + Image.memory( + base64Decode(snapshot.data!.favicon), width: 16, height: 16, ), diff --git a/pubspec.lock b/pubspec.lock index 2a9e8b8..e9150d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -558,7 +558,7 @@ packages: source: hosted version: "0.15.4" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba diff --git a/pubspec.yaml b/pubspec.yaml index 4bd03dd..9b950f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: flutter_dotenv: ^5.1.0 favicon: ^1.1.2 skeletonizer: ^1.0.1 + http: ^1.2.0 dev_dependencies: build_runner: ^2.4.8