Improved favicons load
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
/// Locales: 2
|
/// Locales: 2
|
||||||
/// Strings: 234 (117 per locale)
|
/// 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
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:linkdy/constants/shared_preferences_keys.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:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import 'package:linkdy/providers/shared_preferences.provider.dart';
|
import 'package:linkdy/providers/shared_preferences.provider.dart';
|
||||||
@@ -54,6 +55,7 @@ class AppStatus extends _$AppStatus {
|
|||||||
void setShowFavicon(bool value) {
|
void setShowFavicon(bool value) {
|
||||||
state.showFavicon = value;
|
state.showFavicon = value;
|
||||||
ref.read(sharedPreferencesProvider).setBool(SharedPreferencesKeys.showFavicon, value);
|
ref.read(sharedPreferencesProvider).setBool(SharedPreferencesKeys.showFavicon, value);
|
||||||
|
ref.read(faviconStoreProvider.notifier).clearFavicons();
|
||||||
ref.notifyListeners();
|
ref.notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ final selectedThemeProvider = AutoDisposeProvider<ThemeMode>.internal(
|
|||||||
);
|
);
|
||||||
|
|
||||||
typedef SelectedThemeRef = AutoDisposeProviderRef<ThemeMode>;
|
typedef SelectedThemeRef = AutoDisposeProviderRef<ThemeMode>;
|
||||||
String _$appStatusHash() => r'3b87acee218adcc5707acfb30fd6e5c650b410ce';
|
String _$appStatusHash() => r'f9f4e14943668e49749dfb7e65b42add404e9e8e';
|
||||||
|
|
||||||
/// See also [AppStatus].
|
/// See also [AppStatus].
|
||||||
@ProviderFor(AppStatus)
|
@ProviderFor(AppStatus)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import 'package:linkdy/models/favicon_item.dart';
|
|||||||
|
|
||||||
class FaviconLoaderModel {
|
class FaviconLoaderModel {
|
||||||
List<FaviconItem> favicons;
|
List<FaviconItem> favicons;
|
||||||
|
bool loadingFavicons;
|
||||||
|
|
||||||
FaviconLoaderModel({
|
FaviconLoaderModel({
|
||||||
required this.favicons,
|
required this.favicons,
|
||||||
|
this.loadingFavicons = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
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/screens/bookmarks/model/bookmarks.model.dart';
|
||||||
|
|
||||||
import 'package:linkdy/providers/api_client.provider.dart';
|
import 'package:linkdy/providers/api_client.provider.dart';
|
||||||
@@ -15,6 +16,7 @@ FutureOr<void> bookmarksRequest(BookmarksRequestRef ref, int limit) async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
|
ref.read(faviconStoreProvider.notifier).loadFavicons(result.content!.results!);
|
||||||
ref.read(bookmarksProvider).bookmarks = result.content!.results!;
|
ref.read(bookmarksProvider).bookmarks = result.content!.results!;
|
||||||
ref.read(bookmarksProvider).maxNumber = result.content!.count!;
|
ref.read(bookmarksProvider).maxNumber = result.content!.count!;
|
||||||
ref.read(bookmarksProvider).currentPage = 0;
|
ref.read(bookmarksProvider).currentPage = 0;
|
||||||
@@ -38,6 +40,7 @@ FutureOr<void> bookmarksRequestLoadMore(BookmarksRequestLoadMoreRef ref) async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result.successful == true) {
|
if (result.successful == true) {
|
||||||
|
ref.read(faviconStoreProvider.notifier).loadFavicons(result.content!.results!);
|
||||||
provider.bookmarks = [...provider.bookmarks, ...result.content!.results!];
|
provider.bookmarks = [...provider.bookmarks, ...result.content!.results!];
|
||||||
provider.maxNumber = result.content!.count!;
|
provider.maxNumber = result.content!.count!;
|
||||||
provider.currentPage = provider.currentPage + 1;
|
provider.currentPage = provider.currentPage + 1;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'bookmarks.provider.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$bookmarksRequestHash() => r'04ee313bc986046f5c401f7539b0ba2386001303';
|
String _$bookmarksRequestHash() => r'2da88ec52b4f959e6355f5f5fa8660d6cebd157d';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@@ -156,7 +156,7 @@ class _BookmarksRequestProviderElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$bookmarksRequestLoadMoreHash() =>
|
String _$bookmarksRequestLoadMoreHash() =>
|
||||||
r'523edffa3f18851e1879948e7f8996904aa0aa32';
|
r'36b0703684218e1c6bf01643788d5f9d3da32327';
|
||||||
|
|
||||||
/// See also [bookmarksRequestLoadMore].
|
/// See also [bookmarksRequestLoadMore].
|
||||||
@ProviderFor(bookmarksRequestLoadMore)
|
@ProviderFor(bookmarksRequestLoadMore)
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
import 'dart:convert';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:favicon/favicon.dart';
|
import 'package:favicon/favicon.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import 'package:linkdy/screens/bookmarks/model/favicon_loader.model.dart';
|
import 'package:linkdy/screens/bookmarks/model/favicon_loader.model.dart';
|
||||||
|
|
||||||
import 'package:linkdy/models/favicon_item.dart';
|
import 'package:linkdy/models/favicon_item.dart';
|
||||||
|
import 'package:linkdy/models/data/bookmarks.dart';
|
||||||
|
|
||||||
part 'favicon_loader.provider.g.dart';
|
part 'favicon_loader.provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
// @riverpod
|
||||||
FutureOr<FaviconItem?> fetchFavicon(FetchFaviconRef ref, String bookmarkUrl) async {
|
// FutureOr<FaviconItem?> fetchFavicon(FetchFaviconRef ref, String bookmarkUrl) async {
|
||||||
final exists = ref.watch(faviconStoreProvider).favicons.where((favicon) => favicon.url == bookmarkUrl).toList();
|
// final exists = ref.watch(faviconStoreProvider).favicons.where((favicon) => favicon.url == bookmarkUrl).toList();
|
||||||
if (exists.isNotEmpty) {
|
// if (exists.isNotEmpty) {
|
||||||
return exists[0];
|
// return exists[0];
|
||||||
}
|
// }
|
||||||
|
|
||||||
final faviconResult = await FaviconFinder.getBest(bookmarkUrl);
|
// final faviconResult = await FaviconFinder.getBest(bookmarkUrl);
|
||||||
if (faviconResult == null) {
|
// if (faviconResult == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
final result = await http.get(Uri.parse(faviconResult.url));
|
// final result = await http.get(Uri.parse(faviconResult.url));
|
||||||
if (result.statusCode == 200) {
|
// if (result.statusCode == 200) {
|
||||||
final faviconItem = FaviconItem(
|
// final faviconItem = FaviconItem(
|
||||||
url: bookmarkUrl,
|
// url: bookmarkUrl,
|
||||||
favicon: faviconResult.url.contains("svg") ? result.body : base64.encode(result.bodyBytes),
|
// favicon: faviconResult.url.contains("svg") ? result.body : base64.encode(result.bodyBytes),
|
||||||
isSvg: faviconResult.url.contains("svg"),
|
// isSvg: faviconResult.url.contains("svg"),
|
||||||
);
|
// );
|
||||||
ref.read(faviconStoreProvider).favicons.add(faviconItem);
|
// ref.read(faviconStoreProvider).favicons.add(faviconItem);
|
||||||
return faviconItem;
|
// return faviconItem;
|
||||||
} else {
|
// } else {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class FaviconStore extends _$FaviconStore {
|
class FaviconStore extends _$FaviconStore {
|
||||||
@@ -44,4 +43,33 @@ class FaviconStore extends _$FaviconStore {
|
|||||||
favicons: [],
|
favicons: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearFavicons() {
|
||||||
|
state.favicons = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadFavicons(List<Bookmark> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,157 +6,7 @@ part of 'favicon_loader.provider.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$fetchFaviconHash() => r'90d9f0060aa68162174a1d76a6faeee5ae820494';
|
String _$faviconStoreHash() => r'87cd0d0c0df889ca3238b6c05389b8dd351d189c';
|
||||||
|
|
||||||
/// 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].
|
/// See also [FaviconStore].
|
||||||
@ProviderFor(FaviconStore)
|
@ProviderFor(FaviconStore)
|
||||||
|
|||||||
@@ -46,11 +46,10 @@ class BookmarkItem extends ConsumerWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (ref.watch(appStatusProvider).showFavicon == true)
|
if (ref.watch(appStatusProvider).showFavicon == true)
|
||||||
FutureBuilder(
|
Padding(
|
||||||
future: ref.read(fetchFaviconProvider(bookmark.url!).future),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
builder: (context, snapshot) {
|
child: ref.watch(faviconStoreProvider).loadingFavicons == true
|
||||||
if (snapshot.hasData == false) {
|
? Padding(
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
@@ -64,26 +63,59 @@ class BookmarkItem extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: 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 Row(
|
return child;
|
||||||
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(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
Reference in New Issue
Block a user