improve icon handling
This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
@@ -57,6 +56,12 @@ const _typeMap = {
|
|||||||
"msapplication-TileImage": IconType.microsoftTile,
|
"msapplication-TileImage": IconType.microsoftTile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final class _InFlightFetch {
|
||||||
|
final Future<Result<WebPageInfo>> future;
|
||||||
|
|
||||||
|
const _InFlightFetch(this.future);
|
||||||
|
}
|
||||||
|
|
||||||
Iterable<ResourceSize> sizesToList(String? sizes) sync* {
|
Iterable<ResourceSize> sizesToList(String? sizes) sync* {
|
||||||
if (sizes != null) {
|
if (sizes != null) {
|
||||||
final splitted = sizes
|
final splitted = sizes
|
||||||
@@ -82,12 +87,9 @@ Iterable<ResourceSize> sizesToList(String? sizes) sync* {
|
|||||||
class GenericWebsiteService extends _$GenericWebsiteService {
|
class GenericWebsiteService extends _$GenericWebsiteService {
|
||||||
final GeckoIconService _iconsService;
|
final GeckoIconService _iconsService;
|
||||||
|
|
||||||
//Global icon cache
|
|
||||||
late CacheRepository _cacheRepository;
|
late CacheRepository _cacheRepository;
|
||||||
//Local decoded icon cache
|
|
||||||
late final LRUCache<String, BrowserIcon> _browserIconCache;
|
late final LRUCache<String, BrowserIcon> _browserIconCache;
|
||||||
//In-flight fetch deduplication
|
final _inFlightFetches = <Uri, _InFlightFetch>{};
|
||||||
final _inFlightFetches = <Uri, Future<Result<WebPageInfo>>>{};
|
|
||||||
|
|
||||||
GenericWebsiteService()
|
GenericWebsiteService()
|
||||||
: _iconsService = GeckoIconService(),
|
: _iconsService = GeckoIconService(),
|
||||||
@@ -127,6 +129,10 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
|||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _isHttpUrl(Uri url) {
|
||||||
|
return url.scheme.startsWith('https') || url.scheme.startsWith('http');
|
||||||
|
}
|
||||||
|
|
||||||
static List<Resource> _extractIcons(Uri baseUrl, Document document) {
|
static List<Resource> _extractIcons(Uri baseUrl, Document document) {
|
||||||
final List<Resource> icons = [];
|
final List<Resource> icons = [];
|
||||||
|
|
||||||
@@ -293,7 +299,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<BrowserIcon?> getCachedIcon(Uri url) async {
|
Future<BrowserIcon?> getCachedIcon(Uri url) async {
|
||||||
if (url.scheme.startsWith('https') || url.scheme.startsWith('http')) {
|
if (_isHttpUrl(url)) {
|
||||||
final cachedBrowserIcon = _browserIconCache.get(url.origin);
|
final cachedBrowserIcon = _browserIconCache.get(url.origin);
|
||||||
if (cachedBrowserIcon?.image.value != null) {
|
if (cachedBrowserIcon?.image.value != null) {
|
||||||
return cachedBrowserIcon;
|
return cachedBrowserIcon;
|
||||||
@@ -346,14 +352,38 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<WebPageInfo>> _deduplicatedFetchPageInfo(Uri url) {
|
Future<Result<WebPageInfo>> _deduplicatedFetchPageInfo(Uri url) {
|
||||||
return _inFlightFetches.putIfAbsent(url, () {
|
final existing = _inFlightFetches[url];
|
||||||
return fetchPageInfo(url: url, isImageRequest: true, proxyPort: null)
|
if (existing != null) {
|
||||||
.whenComplete(() => _inFlightFetches.remove(url));
|
return existing.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
late final Future<Result<WebPageInfo>> inFlightFetch;
|
||||||
|
inFlightFetch = fetchPageInfo(
|
||||||
|
url: url,
|
||||||
|
isImageRequest: true,
|
||||||
|
proxyPort: null,
|
||||||
|
).timeout(
|
||||||
|
const Duration(seconds: 20),
|
||||||
|
onTimeout: () => Result.failure(
|
||||||
|
const ErrorMessage(source: 'icon', message: 'Icon fetch timeout'),
|
||||||
|
),
|
||||||
|
).whenComplete(() {
|
||||||
|
// Only clear this entry if it is still the active in-flight request.
|
||||||
|
if (identical(_inFlightFetches[url]?.future, inFlightFetch)) {
|
||||||
|
_inFlightFetches.remove(url);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_inFlightFetches[url] = _InFlightFetch(inFlightFetch);
|
||||||
|
return inFlightFetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<BrowserIcon?> getUrlIcon(List<Uri> urlList) async {
|
Future<BrowserIcon?> getUrlIcon(List<Uri> urlList) async {
|
||||||
for (final url in urlList) {
|
for (final url in urlList) {
|
||||||
|
if (!_isHttpUrl(url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final cachedIcon = await getCachedIcon(url);
|
final cachedIcon = await getCachedIcon(url);
|
||||||
|
|
||||||
if (cachedIcon != null) {
|
if (cachedIcon != null) {
|
||||||
@@ -365,7 +395,6 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
|||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
if (result.value.favicon case final BrowserIcon favicon) {
|
if (result.value.favicon case final BrowserIcon favicon) {
|
||||||
//If it was a `isImageRequest` hit, we need to cache it at this point
|
|
||||||
if (!_browserIconCache.contains(url.origin)) {
|
if (!_browserIconCache.contains(url.origin)) {
|
||||||
_browserIconCache.set(url.origin, favicon);
|
_browserIconCache.set(url.origin, favicon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ final class GenericWebsiteServiceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$genericWebsiteServiceHash() =>
|
String _$genericWebsiteServiceHash() =>
|
||||||
r'437e1b11b6e63d38151354522deda755d9333c47';
|
r'b320635f651574a500ee6764688ef26b6d39ef8f';
|
||||||
|
|
||||||
abstract class _$GenericWebsiteService extends $Notifier<void> {
|
abstract class _$GenericWebsiteService extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
+4
-9
@@ -44,6 +44,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/toolbar_button.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/toolbar_button.dart';
|
||||||
@@ -57,7 +58,6 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
|||||||
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
||||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||||
import 'package:weblibre/presentation/icons/weblibre_icons.dart';
|
import 'package:weblibre/presentation/icons/weblibre_icons.dart';
|
||||||
import 'package:weblibre/presentation/widgets/safe_raw_image.dart';
|
|
||||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart';
|
import 'package:weblibre/utils/ui_helper.dart';
|
||||||
@@ -567,15 +567,10 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemAvatar: (item) =>
|
itemAvatar: (item) =>
|
||||||
item.tabState?.icon.mapNotNull(
|
item.tabState.mapNotNull(
|
||||||
(icon) => SafeRawImage(
|
(tabState) => TabIcon(tabState: tabState, iconSize: 20),
|
||||||
image: icon,
|
|
||||||
height: 24,
|
|
||||||
width: 24,
|
|
||||||
fallback: UrlIcon([item.url], iconSize: 24),
|
|
||||||
),
|
|
||||||
) ??
|
) ??
|
||||||
UrlIcon([item.url], iconSize: 16),
|
UrlIcon([item.url], iconSize: 20),
|
||||||
itemBackgroundColor: (item) =>
|
itemBackgroundColor: (item) =>
|
||||||
item.color != null ? ContainerColors.forChip(item.color!) : null,
|
item.color != null ? ContainerColors.forChip(item.color!) : null,
|
||||||
onSelected: (item) async {
|
onSelected: (item) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user