prepare for multiple apps

This commit is contained in:
Fabian Freund
2026-04-06 12:23:11 +02:00
parent bd1600e8dc
commit 5afc323f04
904 changed files with 29 additions and 29 deletions
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'browser_extension.g.dart';
@Riverpod(keepAlive: true)
GeckoBrowserExtensionService browserExtensionService(Ref ref) {
final service = GeckoBrowserExtensionService.setUp();
ref.onDispose(() {
service.dispose();
});
return service;
}
@Riverpod()
Stream<String> feedRequested(Ref ref) {
final service = ref.watch(browserExtensionServiceProvider);
return service.feedRequested;
}
@@ -0,0 +1,91 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'browser_extension.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(browserExtensionService)
final browserExtensionServiceProvider = BrowserExtensionServiceProvider._();
final class BrowserExtensionServiceProvider
extends
$FunctionalProvider<
GeckoBrowserExtensionService,
GeckoBrowserExtensionService,
GeckoBrowserExtensionService
>
with $Provider<GeckoBrowserExtensionService> {
BrowserExtensionServiceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'browserExtensionServiceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$browserExtensionServiceHash();
@$internal
@override
$ProviderElement<GeckoBrowserExtensionService> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
GeckoBrowserExtensionService create(Ref ref) {
return browserExtensionService(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(GeckoBrowserExtensionService value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<GeckoBrowserExtensionService>(value),
);
}
}
String _$browserExtensionServiceHash() =>
r'c3f67763e0abb10039b407fced6e175e5ec7c6c3';
@ProviderFor(feedRequested)
final feedRequestedProvider = FeedRequestedProvider._();
final class FeedRequestedProvider
extends $FunctionalProvider<AsyncValue<String>, String, Stream<String>>
with $FutureModifier<String>, $StreamProvider<String> {
FeedRequestedProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'feedRequestedProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$feedRequestedHash();
@$internal
@override
$StreamProviderElement<String> $createElement($ProviderPointer pointer) =>
$StreamProviderElement(pointer);
@override
Stream<String> create(Ref ref) {
return feedRequested(ref);
}
}
String _$feedRequestedHash() => r'4f179d0878072a77a87422ff6afdac53a3d57c04';
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
part 'desktop_mode.g.dart';
@Riverpod(keepAlive: true)
class DesktopMode extends _$DesktopMode {
// ignore: use_setters_to_change_properties
void enabled(bool value) {
state = value;
}
void toggle() {
state = !state;
}
@override
bool build(String tabId) {
listenSelf((previous, next) async {
if (previous != null) {
await ref
.read(tabSessionProvider(tabId: tabId).notifier)
.requestDesktopSite(next);
}
});
return false;
}
}
@@ -0,0 +1,99 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'desktop_mode.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(DesktopMode)
final desktopModeProvider = DesktopModeFamily._();
final class DesktopModeProvider extends $NotifierProvider<DesktopMode, bool> {
DesktopModeProvider._({
required DesktopModeFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'desktopModeProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$desktopModeHash();
@override
String toString() {
return r'desktopModeProvider'
''
'($argument)';
}
@$internal
@override
DesktopMode create() => DesktopMode();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
@override
bool operator ==(Object other) {
return other is DesktopModeProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$desktopModeHash() => r'18550009e95a23ff82d30206ce81daa859c57c26';
final class DesktopModeFamily extends $Family
with $ClassFamilyOverride<DesktopMode, bool, bool, bool, String> {
DesktopModeFamily._()
: super(
retry: null,
name: r'desktopModeProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: false,
);
DesktopModeProvider call(String tabId) =>
DesktopModeProvider._(argument: tabId, from: this);
@override
String toString() => r'desktopModeProvider';
}
abstract class _$DesktopMode extends $Notifier<bool> {
late final _$args = ref.$arg as String;
String get tabId => _$args;
bool build(String tabId);
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<bool, bool>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<bool, bool>,
bool,
Object?,
Object?
>;
element.handleCreate(ref, () => build(_$args));
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
part 'selected_tab.g.dart';
@Riverpod(keepAlive: true)
class SelectedTab extends _$SelectedTab {
@override
String? build() {
final eventSerivce = ref.watch(eventServiceProvider);
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(previous, next) async {
if (next) {
await GeckoTabService().syncEvents(onSelectedTabChange: true);
}
},
onError: (error, stackTrace) {
logger.e(
'Error listening to engineReadyStateProvider',
error: error,
stackTrace: stackTrace,
);
},
);
final selectedTabSub = eventSerivce.selectedTabEvents.listen(
(tabId) {
state = tabId;
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in selected tab events',
error: error,
stackTrace: stackTrace,
);
},
);
ref.onDispose(() async {
await selectedTabSub.cancel();
});
return null;
}
}
@@ -0,0 +1,62 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_tab.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(SelectedTab)
final selectedTabProvider = SelectedTabProvider._();
final class SelectedTabProvider
extends $NotifierProvider<SelectedTab, String?> {
SelectedTabProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedTabProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedTabHash();
@$internal
@override
SelectedTab create() => SelectedTab();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<String?>(value),
);
}
}
String _$selectedTabHash() => r'5caa359376e9103767e2d2e94c548d53cba79d0f';
abstract class _$SelectedTab extends $Notifier<String?> {
String? build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<String?, String?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<String?, String?>,
String?,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
part 'tab_list.g.dart';
@Riverpod(keepAlive: true)
class TabList extends _$TabList {
@override
EquatableValue<List<String>> build() {
final eventService = ref.watch(eventServiceProvider);
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(previous, next) async {
if (next) {
await GeckoTabService().syncEvents(onTabListChange: true);
}
},
onError: (error, stackTrace) {
logger.e(
'Error listening to eventServiceProvider',
error: error,
stackTrace: stackTrace,
);
},
);
final tabListSub = eventService.tabListEvents.listen(
(tabs) {
final equatableTabs = EquatableValue(tabs);
if (equatableTabs != state) {
state = equatableTabs;
}
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in tab list events',
error: error,
stackTrace: stackTrace,
);
},
);
ref.onDispose(() async {
await tabListSub.cancel();
});
return EquatableValue([]);
}
}
@@ -0,0 +1,67 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(TabList)
final tabListProvider = TabListProvider._();
final class TabListProvider
extends $NotifierProvider<TabList, EquatableValue<List<String>>> {
TabListProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'tabListProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabListHash();
@$internal
@override
TabList create() => TabList();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(EquatableValue<List<String>> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<EquatableValue<List<String>>>(value),
);
}
}
String _$tabListHash() => r'88c2598a4d586fee9e3d0f1b418cf88ea5c7da75';
abstract class _$TabList extends $Notifier<EquatableValue<List<String>>> {
EquatableValue<List<String>> build();
@$mustCallSuper
@override
void runBuild() {
final ref =
this.ref
as $Ref<EquatableValue<List<String>>, EquatableValue<List<String>>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<
EquatableValue<List<String>>,
EquatableValue<List<String>>
>,
EquatableValue<List<String>>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'dart:typed_data';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab_session.g.dart';
@Riverpod(keepAlive: true)
class TabSession extends _$TabSession {
late GeckoSessionService _sessionService;
Future<void> loadUrl({
required Uri url,
LoadUrlFlags flags = LoadUrlFlags.NONE,
Map<String, String>? additionalHeaders,
}) {
return _sessionService.loadUrl(
url: url,
flags: flags,
additionalHeaders: additionalHeaders,
);
}
Future<void> stopLoading() {
return _sessionService.stopLoading();
}
Future<void> reload({LoadUrlFlags flags = LoadUrlFlags.NONE}) {
return _sessionService.reload(flags: flags);
}
Future<void> goBack() {
return _sessionService.goBack();
}
Future<void> goForward() {
return _sessionService.goForward();
}
Future<void> goToHistoryIndex({required int index}) {
return _sessionService.goToHistoryIndex(index: index);
}
Future<void> exitFullscreen() {
return _sessionService.exitFullscreen();
}
Future<Uint8List?> requestScreenshot({bool requireImageResult = true}) {
return _sessionService.requestScreenshot(requireImageResult);
}
Future<void> requestDesktopSite(bool enable) {
return _sessionService.requestDesktopSite(enable: enable);
}
Future<void> saveToPdf() {
return _sessionService.saveToPdf();
}
Future<void> printContent() {
return _sessionService.printContent();
}
Future<void> translate({
required String fromLanguage,
required String toLanguage,
bool? downloadModel,
}) {
return _sessionService.translate(
fromLanguage: fromLanguage,
toLanguage: toLanguage,
options: TranslationOptions(downloadModel: downloadModel ?? true),
);
}
Future<void> translateRestore() {
return _sessionService.translateRestore();
}
Future<void> pageUp() {
// Android KeyEvent.KEYCODE_PAGE_UP = 92
return _sessionService.dispatchKeyEvent(keyCode: 92);
}
Future<void> pageDown() {
// Android KeyEvent.KEYCODE_PAGE_DOWN = 93
return _sessionService.dispatchKeyEvent(keyCode: 93);
}
Future<void> scrollToTop() {
// Android KeyEvent.KEYCODE_MOVE_HOME = 122
return _sessionService.dispatchKeyEvent(keyCode: 122);
}
Future<void> scrollToBottom() {
// Android KeyEvent.KEYCODE_MOVE_END = 123
return _sessionService.dispatchKeyEvent(keyCode: 123);
}
@override
void build({required String? tabId}) {
_sessionService = (tabId != null)
? GeckoSessionService(tabId: tabId)
: GeckoSessionService.forActiveTab();
}
}
@Riverpod(keepAlive: true)
Raw<TabSession> selectedTabSessionNotifier(Ref ref) {
return ref.watch(tabSessionProvider(tabId: null).notifier);
}
@@ -0,0 +1,142 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_session.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(TabSession)
final tabSessionProvider = TabSessionFamily._();
final class TabSessionProvider extends $NotifierProvider<TabSession, void> {
TabSessionProvider._({
required TabSessionFamily super.from,
required String? super.argument,
}) : super(
retry: null,
name: r'tabSessionProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabSessionHash();
@override
String toString() {
return r'tabSessionProvider'
''
'($argument)';
}
@$internal
@override
TabSession create() => TabSession();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
@override
bool operator ==(Object other) {
return other is TabSessionProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$tabSessionHash() => r'636f0940425d689ff98fabfc61687de828a8eeb9';
final class TabSessionFamily extends $Family
with $ClassFamilyOverride<TabSession, void, void, void, String?> {
TabSessionFamily._()
: super(
retry: null,
name: r'tabSessionProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: false,
);
TabSessionProvider call({required String? tabId}) =>
TabSessionProvider._(argument: tabId, from: this);
@override
String toString() => r'tabSessionProvider';
}
abstract class _$TabSession extends $Notifier<void> {
late final _$args = ref.$arg as String?;
String? get tabId => _$args;
void build({required String? tabId});
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleCreate(ref, () => build(tabId: _$args));
}
}
@ProviderFor(selectedTabSessionNotifier)
final selectedTabSessionProvider = SelectedTabSessionNotifierProvider._();
final class SelectedTabSessionNotifierProvider
extends
$FunctionalProvider<Raw<TabSession>, Raw<TabSession>, Raw<TabSession>>
with $Provider<Raw<TabSession>> {
SelectedTabSessionNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedTabSessionProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedTabSessionNotifierHash();
@$internal
@override
$ProviderElement<Raw<TabSession>> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
Raw<TabSession> create(Ref ref) {
return selectedTabSessionNotifier(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Raw<TabSession> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Raw<TabSession>>(value),
);
}
}
String _$selectedTabSessionNotifierHash() =>
r'3945a3a3faf615e1c95819abda276f3e251d5b4c';
@@ -0,0 +1,467 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/find_result.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/history.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/security.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/translation.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
import 'package:weblibre/features/geckoview/features/find_in_page/domain/repositories/find_in_page.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/isolation_context.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/utils/image_helper.dart';
import 'package:weblibre/features/user/data/models/tor_settings.dart';
import 'package:weblibre/features/user/domain/repositories/tor_settings.dart';
part 'tab_state.g.dart';
@Riverpod(keepAlive: true)
class TabStates extends _$TabStates {
/// Disposes images from a TabState to free GPU memory.
void _disposeTabImages(TabState tab) {
tab.icon?.dispose();
tab.thumbnail?.dispose();
}
/// Updates state while disposing images from removed tabs.
void _updateState(Map<String, TabState> newState) {
// Find and dispose images from tabs that are being removed
for (final tabId in state.keys) {
if (!newState.containsKey(tabId)) {
_disposeTabImages(state[tabId]!);
}
}
state = newState;
}
Future<void> _onTabContentStateChange(TabContentState contentState) async {
final current = await patchedState(contentState.id);
final url = Uri.parse(contentState.url);
// Determine title based on priority: new non-empty title > existing title if URL authority unchanged > new title
final String resolvedTitle;
if (contentState.title.isNotEmpty) {
resolvedTitle = contentState.title;
} else if (current.url.authority == url.authority) {
resolvedTitle = current.title;
} else {
resolvedTitle = contentState.title;
}
// Infer tabMode from context ID if not already set and context is isolated
final inferredTabMode = switch (current.tabMode) {
IsolatedTabMode() => current.tabMode,
_ when isIsolatedContextId(contentState.contextId) => TabMode.isolated(
contentState.contextId!,
),
_ when contentState.isPrivate => TabMode.private,
_ => TabMode.regular,
};
final newState = current.copyWith(
parentId: contentState.parentId,
contextId: contentState.contextId,
url: url,
title: resolvedTitle,
progress: contentState.progress,
tabMode: inferredTabMode,
isFullScreen: contentState.isFullScreen,
isLoading: contentState.isLoading,
showToolbarAsExpanded: contentState.showToolbarAsExpanded,
);
_updateState({...state}..[contentState.id] = newState);
if (newState.isFinishedLoading) {
ref
.read(geckoInferenceRepositoryProvider.notifier)
.markInitialLoadComplete();
}
}
Future<TabState> patchedState(String id) async {
var current = stateOrNull?[id];
if (current == null || current.url == TabState.defaultUrl) {
current ??= TabState.$default(id);
final tabData = await ref
.read(tabDataRepositoryProvider.notifier)
.getTabDataById(id);
if (tabData?.url != null) {
current = current.copyWith(
title: tabData!.title ?? current.title,
url: tabData.url ?? current.url,
);
}
}
return current;
}
Future<void> _onIconChange(IconChangeEvent event) async {
final IconChangeEvent(:tabId, :bytes) = event;
final image = await bytes.mapNotNull((bytes) => tryDecodeImage(bytes));
final current = state[tabId] ?? TabState.$default(tabId);
// Dispose old icon only after successfully creating new one
current.icon?.dispose();
state = {...state}..[tabId] = current.copyWith.icon(image);
}
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
final ThumbnailEvent(:tabId, :bytes) = event;
final image = await bytes.mapNotNull((bytes) => tryDecodeImage(bytes));
final current = state[tabId] ?? TabState.$default(tabId);
// Dispose old thumbnail only after successfully creating new one
current.thumbnail?.dispose();
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
}
void _onSecurityInfoStateChange(SecurityInfoEvent event) {
final SecurityInfoEvent(:tabId, :securityInfo) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}
..[tabId] = current.copyWith.securityInfoState(
SecurityState(
secure: securityInfo.secure,
host: securityInfo.host,
issuer: securityInfo.issuer,
),
);
}
void _onHistoryStateChange(HistoryEvent event) {
final HistoryEvent(:tabId, :history) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}
..[tabId] = current.copyWith.historyState(
HistoryState(
items: history.items.nonNulls
.map(
(item) =>
HistoryItem(url: Uri.parse(item.url), title: item.title),
)
.toList(),
currentIndex: history.currentIndex,
canGoBack: history.canGoBack,
canGoForward: history.canGoForward,
),
);
}
void _onReaderableStateChange(ReaderableEvent event) {
final ReaderableEvent(:tabId, :readerable) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}
..[tabId] = current.copyWith.readerableState(
ReaderableState(
readerable: readerable.readerable,
active: readerable.active,
),
);
}
void _onTabTranslationStateChange(TabTranslationEvent event) {
final TabTranslationEvent(:tabId, :state) = event;
final current = this.state[tabId] ?? TabState.$default(tabId);
this.state = {...this.state}
..[tabId] = current.copyWith.translationState(
TranslationState.fromData(state),
);
}
void _onFindResultsChange(FindResultsEvent event) {
final FindResultsEvent(:tabId, :results) = event;
final current = state[tabId] ?? TabState.$default(tabId);
if (results.isNotEmpty) {
final result = results.last;
state = {...state}
..[tabId] = current.copyWith.findResultState(
FindResultState(
lastSearchText: ref.read(findInPageRepositoryProvider(tabId)),
activeMatchOrdinal: result.activeMatchOrdinal,
numberOfMatches: result.numberOfMatches,
isDoneCounting: result.isDoneCounting,
),
);
} else if (current.findResultState.hasMatches) {
state = {
...state,
}..[tabId] = current.copyWith.findResultState(FindResultState.$default());
}
}
@override
Map<String, TabState> build() {
final eventService = ref.watch(eventServiceProvider);
final subscriptions = [
eventService.tabContentEvents.listen(
(event) async {
await _onTabContentStateChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in tab content events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.iconChangeEvents.listen(
(event) async {
await _onIconChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in icon change events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.thumbnailEvents.listen(
(event) async {
await _onThumbnailChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in thumbnail events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.securityInfoEvents.listen(
(event) {
_onSecurityInfoStateChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in security info events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.historyEvents.listen(
(event) {
_onHistoryStateChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in history events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.readerableEvents.listen(
(event) {
_onReaderableStateChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in readerable events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.findResultsEvent
.debounceTime(const Duration(milliseconds: 25))
.listen(
(event) {
_onFindResultsChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in find results events',
error: error,
stackTrace: stackTrace,
);
},
),
eventService.tabTranslationEvents.listen(
(event) {
_onTabTranslationStateChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in tab translation events',
error: error,
stackTrace: stackTrace,
);
},
),
];
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(previous, next) async {
if (next) {
await GeckoTabService().syncEvents(
onTabContentStateChange: true,
onIconChange: true,
onThumbnailChange: true,
onSecurityInfoStateChange: true,
onHistoryStateChange: true,
onFindResults: true,
onTranslationStateChange: true,
);
}
},
onError: (error, stackTrace) {
logger.e(
'Error listening to engineReadyStateProvider',
error: error,
stackTrace: stackTrace,
);
},
);
ref.onDispose(() async {
// Dispose all remaining tab images
for (final tab in state.values) {
_disposeTabImages(tab);
}
// Cancel all stream subscriptions
for (final sub in subscriptions) {
await sub.cancel();
}
});
return {};
}
}
@Riverpod()
TabState? tabState(Ref ref, String? tabId) {
if (tabId == null) {
return null;
}
return ref.watch(tabStatesProvider.select((tabs) => tabs[tabId]));
}
@Riverpod()
Future<TabState> tabStateWithFallback(Ref ref, String tabId) async {
final state = ref.watch(tabStateProvider(tabId));
if (state != null) {
return state;
}
return await ref.read(tabStatesProvider.notifier).patchedState(tabId);
}
@Riverpod()
Future<bool> isTabTunneled(Ref ref, String? tabId) async {
final tabState = ref.watch(tabStateProvider(tabId));
final torSettings = ref.watch(torSettingsWithDefaultsProvider);
if (tabState != null) {
// Isolated tabs follow the same proxy rules as regular tabs
// (container-based routing via proxy aliasing)
if (tabState.tabMode is PrivateTabMode) {
return torSettings.proxyPrivateTabsTor;
} else {
switch (torSettings.proxyRegularTabsMode) {
case TorRegularTabProxyMode.container:
final containerData = await ref
.read(tabDataRepositoryProvider.notifier)
.getTabContainerData(tabState.id);
if (!ref.mounted) return false;
return containerData?.metadata.useProxy ?? false;
case TorRegularTabProxyMode.all:
return true;
}
}
}
return false;
}
@Riverpod()
TabState? selectedTabState(Ref ref) {
final tabId = ref.watch(selectedTabProvider);
return ref.watch(tabStateProvider(tabId));
}
@Riverpod()
TabType? selectedTabType(Ref ref) {
final selectedState = ref.watch(selectedTabStateProvider);
return selectedState?.tabMode.toTabType();
}
@Riverpod()
AsyncValue<String?> selectedTabContainerId(Ref ref) {
final tabId = ref.watch(selectedTabProvider);
if (tabId != null) {
return ref.watch(watchContainerTabIdProvider(tabId));
}
return const AsyncData(null);
}
// @Riverpod()
// Stream<int> tabScrollY(Ref ref, String? tabId, Duration sampleTime) {
// final eventService = ref.watch(eventServiceProvider);
// return eventService.scrollEvent
// .where((event) => event.tabId == tabId)
// .sampleTime(sampleTime)
// .map((event) => event.scrollY)
// .asBroadcastStream();
// }
@@ -0,0 +1,409 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_state.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(TabStates)
final tabStatesProvider = TabStatesProvider._();
final class TabStatesProvider
extends $NotifierProvider<TabStates, Map<String, TabState>> {
TabStatesProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'tabStatesProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabStatesHash();
@$internal
@override
TabStates create() => TabStates();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Map<String, TabState> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Map<String, TabState>>(value),
);
}
}
String _$tabStatesHash() => r'6fc9a91b1343f9ae0b442100bed20e42448afa37';
abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
Map<String, TabState> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<Map<String, TabState>, Map<String, TabState>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<Map<String, TabState>, Map<String, TabState>>,
Map<String, TabState>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@ProviderFor(tabState)
final tabStateProvider = TabStateFamily._();
final class TabStateProvider
extends $FunctionalProvider<TabState?, TabState?, TabState?>
with $Provider<TabState?> {
TabStateProvider._({
required TabStateFamily super.from,
required String? super.argument,
}) : super(
retry: null,
name: r'tabStateProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabStateHash();
@override
String toString() {
return r'tabStateProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<TabState?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
TabState? create(Ref ref) {
final argument = this.argument as String?;
return tabState(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(TabState? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<TabState?>(value),
);
}
@override
bool operator ==(Object other) {
return other is TabStateProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$tabStateHash() => r'f57b2353acc99ca73670d504b45ea5ff19df38c7';
final class TabStateFamily extends $Family
with $FunctionalFamilyOverride<TabState?, String?> {
TabStateFamily._()
: super(
retry: null,
name: r'tabStateProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
TabStateProvider call(String? tabId) =>
TabStateProvider._(argument: tabId, from: this);
@override
String toString() => r'tabStateProvider';
}
@ProviderFor(tabStateWithFallback)
final tabStateWithFallbackProvider = TabStateWithFallbackFamily._();
final class TabStateWithFallbackProvider
extends
$FunctionalProvider<AsyncValue<TabState>, TabState, FutureOr<TabState>>
with $FutureModifier<TabState>, $FutureProvider<TabState> {
TabStateWithFallbackProvider._({
required TabStateWithFallbackFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'tabStateWithFallbackProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabStateWithFallbackHash();
@override
String toString() {
return r'tabStateWithFallbackProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<TabState> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<TabState> create(Ref ref) {
final argument = this.argument as String;
return tabStateWithFallback(ref, argument);
}
@override
bool operator ==(Object other) {
return other is TabStateWithFallbackProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$tabStateWithFallbackHash() =>
r'061011ac79738dbe4e662a76fccbc8592cba21cf';
final class TabStateWithFallbackFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<TabState>, String> {
TabStateWithFallbackFamily._()
: super(
retry: null,
name: r'tabStateWithFallbackProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
TabStateWithFallbackProvider call(String tabId) =>
TabStateWithFallbackProvider._(argument: tabId, from: this);
@override
String toString() => r'tabStateWithFallbackProvider';
}
@ProviderFor(isTabTunneled)
final isTabTunneledProvider = IsTabTunneledFamily._();
final class IsTabTunneledProvider
extends $FunctionalProvider<AsyncValue<bool>, bool, FutureOr<bool>>
with $FutureModifier<bool>, $FutureProvider<bool> {
IsTabTunneledProvider._({
required IsTabTunneledFamily super.from,
required String? super.argument,
}) : super(
retry: null,
name: r'isTabTunneledProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$isTabTunneledHash();
@override
String toString() {
return r'isTabTunneledProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<bool> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<bool> create(Ref ref) {
final argument = this.argument as String?;
return isTabTunneled(ref, argument);
}
@override
bool operator ==(Object other) {
return other is IsTabTunneledProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$isTabTunneledHash() => r'b11134b33ad0c98ffddd14afc2c790fb65735e93';
final class IsTabTunneledFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<bool>, String?> {
IsTabTunneledFamily._()
: super(
retry: null,
name: r'isTabTunneledProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
IsTabTunneledProvider call(String? tabId) =>
IsTabTunneledProvider._(argument: tabId, from: this);
@override
String toString() => r'isTabTunneledProvider';
}
@ProviderFor(selectedTabState)
final selectedTabStateProvider = SelectedTabStateProvider._();
final class SelectedTabStateProvider
extends $FunctionalProvider<TabState?, TabState?, TabState?>
with $Provider<TabState?> {
SelectedTabStateProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedTabStateProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedTabStateHash();
@$internal
@override
$ProviderElement<TabState?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
TabState? create(Ref ref) {
return selectedTabState(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(TabState? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<TabState?>(value),
);
}
}
String _$selectedTabStateHash() => r'dbd36af7af286bd9d079f30e8e11bbda23bf7728';
@ProviderFor(selectedTabType)
final selectedTabTypeProvider = SelectedTabTypeProvider._();
final class SelectedTabTypeProvider
extends $FunctionalProvider<TabType?, TabType?, TabType?>
with $Provider<TabType?> {
SelectedTabTypeProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedTabTypeProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedTabTypeHash();
@$internal
@override
$ProviderElement<TabType?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
TabType? create(Ref ref) {
return selectedTabType(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(TabType? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<TabType?>(value),
);
}
}
String _$selectedTabTypeHash() => r'4cf99c9175357a7835fc3c4eff29a9f328931e4f';
@ProviderFor(selectedTabContainerId)
final selectedTabContainerIdProvider = SelectedTabContainerIdProvider._();
final class SelectedTabContainerIdProvider
extends
$FunctionalProvider<
AsyncValue<String?>,
AsyncValue<String?>,
AsyncValue<String?>
>
with $Provider<AsyncValue<String?>> {
SelectedTabContainerIdProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedTabContainerIdProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedTabContainerIdHash();
@$internal
@override
$ProviderElement<AsyncValue<String?>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
AsyncValue<String?> create(Ref ref) {
return selectedTabContainerId(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AsyncValue<String?> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AsyncValue<String?>>(value),
);
}
}
String _$selectedTabContainerIdHash() =>
r'07899d29f69654d3b314d0da945ad402b1003b41';
@@ -0,0 +1,200 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
import 'dart:async';
import 'dart:ui';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/domain/entities/equatable_image.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/web_extension.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
import 'package:weblibre/features/geckoview/utils/image_helper.dart';
import 'package:weblibre/utils/lru_cache.dart';
part 'web_extensions_state.g.dart';
@Riverpod(keepAlive: true)
class WebExtensionsState extends _$WebExtensionsState {
late final LRUCache<String, EquatableImage> _imageCache;
WebExtensionsState()
: _imageCache = LRUCache(50, onEvict: (image) => image.dispose());
void _onExtensionUpdate(ExtensionDataEvent event) {
final ExtensionDataEvent(:extensionId, :data) = event;
if (data != null) {
final cachedIcon = _imageCache.get(extensionId);
if (cachedIcon != null && cachedIcon.value == null) {
_imageCache.remove(extensionId);
}
final current =
state[extensionId] ??
WebExtensionState(
extensionId: extensionId,
icon: _imageCache.get(extensionId),
enabled: false,
);
state = {...state}
..[extensionId] = current.copyWith(
title: data.title,
enabled: data.enabled ?? current.enabled,
badgeText: data.badgeText,
badgeTextColor: data.badgeTextColor != null
? Color(data.badgeTextColor!)
: null,
badgeBackgroundColor: data.badgeBackgroundColor != null
? Color(data.badgeBackgroundColor!)
: null,
);
} else {
if (state.containsKey(extensionId)) {
state = {...state}..remove(extensionId);
// remove() triggers onEvict which handles disposal
_imageCache.remove(extensionId);
}
}
}
Future<void> _onIconChange(ExtensionIconEvent event) async {
final ExtensionIconEvent(:extensionId, :bytes) = event;
final image = await tryDecodeImage(bytes);
if (image != null) {
// set() will evict the old entry via onEvict callback, which handles disposal
_imageCache.set(extensionId, image);
if (state.containsKey(extensionId)) {
state = {...state}
..[extensionId] = state[extensionId]!.copyWith.icon(image);
}
}
}
@override
Map<String, WebExtensionState> build(WebExtensionActionType actionType) {
final addonService = ref.watch(addonServiceProvider);
final subscriptions = switch (actionType) {
WebExtensionActionType.browser => [
addonService.browserExtensionStream.listen(
(event) {
_onExtensionUpdate(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in browser extension stream',
error: error,
stackTrace: stackTrace,
);
},
),
addonService.browserIconStream.listen(
(event) async {
await _onIconChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in browser icon stream',
error: error,
stackTrace: stackTrace,
);
},
),
],
WebExtensionActionType.page => [
addonService.pageExtensionStream.listen(
(event) {
_onExtensionUpdate(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in page extension stream',
error: error,
stackTrace: stackTrace,
);
},
),
addonService.pageIconStream.listen(
(event) async {
await _onIconChange(event);
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in page icon stream',
error: error,
stackTrace: stackTrace,
);
},
),
],
};
// Sync extension events after engine is ready and listeners are set up
// This ensures we get the current state even if we missed initial events
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(previous, next) async {
if (next) {
try {
await GeckoTabService().syncEvents(
onBrowserExtensionsChange:
actionType == WebExtensionActionType.browser,
onPageExtensionsChange: actionType == WebExtensionActionType.page,
onBrowserExtensionIcons:
actionType == WebExtensionActionType.browser,
onPageExtensionIcons: actionType == WebExtensionActionType.page,
);
} catch (e, s) {
logger.w(
'Failed to sync extension events for $actionType',
error: e,
stackTrace: s,
);
}
}
},
onError: (error, stackTrace) {
logger.e(
'Error listening to engineReadyStateProvider',
error: error,
stackTrace: stackTrace,
);
},
);
ref.onDispose(() async {
// Dispose all cached images
_imageCache.clear();
// Cancel all stream subscriptions
for (final sub in subscriptions) {
await sub.cancel();
}
});
return {};
}
}
@@ -0,0 +1,120 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'web_extensions_state.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(WebExtensionsState)
final webExtensionsStateProvider = WebExtensionsStateFamily._();
final class WebExtensionsStateProvider
extends
$NotifierProvider<WebExtensionsState, Map<String, WebExtensionState>> {
WebExtensionsStateProvider._({
required WebExtensionsStateFamily super.from,
required WebExtensionActionType super.argument,
}) : super(
retry: null,
name: r'webExtensionsStateProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$webExtensionsStateHash();
@override
String toString() {
return r'webExtensionsStateProvider'
''
'($argument)';
}
@$internal
@override
WebExtensionsState create() => WebExtensionsState();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Map<String, WebExtensionState> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Map<String, WebExtensionState>>(
value,
),
);
}
@override
bool operator ==(Object other) {
return other is WebExtensionsStateProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$webExtensionsStateHash() =>
r'13cbbea409b5b643eb6766689f6b23a4d1701204';
final class WebExtensionsStateFamily extends $Family
with
$ClassFamilyOverride<
WebExtensionsState,
Map<String, WebExtensionState>,
Map<String, WebExtensionState>,
Map<String, WebExtensionState>,
WebExtensionActionType
> {
WebExtensionsStateFamily._()
: super(
retry: null,
name: r'webExtensionsStateProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: false,
);
WebExtensionsStateProvider call(WebExtensionActionType actionType) =>
WebExtensionsStateProvider._(argument: actionType, from: this);
@override
String toString() => r'webExtensionsStateProvider';
}
abstract class _$WebExtensionsState
extends $Notifier<Map<String, WebExtensionState>> {
late final _$args = ref.$arg as WebExtensionActionType;
WebExtensionActionType get actionType => _$args;
Map<String, WebExtensionState> build(WebExtensionActionType actionType);
@$mustCallSuper
@override
void runBuild() {
final ref =
this.ref
as $Ref<
Map<String, WebExtensionState>,
Map<String, WebExtensionState>
>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<
Map<String, WebExtensionState>,
Map<String, WebExtensionState>
>,
Map<String, WebExtensionState>,
Object?,
Object?
>;
element.handleCreate(ref, () => build(_$args));
}
}