Cache favicons
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
11
lib/models/favicon_item.dart
Normal file
11
lib/models/favicon_item.dart
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
10
lib/providers/temporary_directory.provider.dart
Normal file
10
lib/providers/temporary_directory.provider.dart
Normal file
@@ -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();
|
||||
}
|
||||
26
lib/providers/temporary_directory.provider.g.dart
Normal file
26
lib/providers/temporary_directory.provider.g.dart
Normal file
@@ -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<Directory>.internal(
|
||||
temporaryDirectory,
|
||||
name: r'temporaryDirectoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$temporaryDirectoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef TemporaryDirectoryRef = ProviderRef<Directory>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
9
lib/screens/bookmarks/model/favicon_loader.model.dart
Normal file
9
lib/screens/bookmarks/model/favicon_loader.model.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:linkdy/models/favicon_item.dart';
|
||||
|
||||
class FaviconLoaderModel {
|
||||
List<FaviconItem> favicons;
|
||||
|
||||
FaviconLoaderModel({
|
||||
required this.favicons,
|
||||
});
|
||||
}
|
||||
47
lib/screens/bookmarks/provider/favicon_loader.provider.dart
Normal file
47
lib/screens/bookmarks/provider/favicon_loader.provider.dart
Normal file
@@ -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<FaviconItem?> 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: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
175
lib/screens/bookmarks/provider/favicon_loader.provider.g.dart
Normal file
175
lib/screens/bookmarks/provider/favicon_loader.provider.g.dart
Normal file
@@ -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<AsyncValue<FaviconItem?>> {
|
||||
/// 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<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'fetchFaviconProvider';
|
||||
}
|
||||
|
||||
/// See also [fetchFavicon].
|
||||
class FetchFaviconProvider extends AutoDisposeFutureProvider<FaviconItem?> {
|
||||
/// 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<FaviconItem?> 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<FaviconItem?> 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<FaviconItem?> {
|
||||
/// The parameter `bookmarkUrl` of this provider.
|
||||
String get bookmarkUrl;
|
||||
}
|
||||
|
||||
class _FetchFaviconProviderElement
|
||||
extends AutoDisposeFutureProviderElement<FaviconItem?>
|
||||
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<FaviconStore, FaviconLoaderModel>.internal(
|
||||
FaviconStore.new,
|
||||
name: r'faviconStoreProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$faviconStoreHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$FaviconStore = Notifier<FaviconLoaderModel>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -558,7 +558,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.15.4"
|
||||
http:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user