performance improvements
This commit is contained in:
@@ -23,15 +23,20 @@ import 'package:nullability/nullability.dart';
|
||||
import 'package:weblibre/data/models/web_page_info.dart';
|
||||
import 'package:weblibre/domain/entities/equatable_image.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/browser_icon.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/translation.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
|
||||
part 'tab.g.dart';
|
||||
|
||||
/// Per-tab state the browser chrome and tab ordering depend on.
|
||||
///
|
||||
/// Deliberately narrow: the whole `Map<String, TabState>` is watched by the
|
||||
/// providers feeding the always-visible quick tab switcher and the grouped tab
|
||||
/// list, so any field added here rebuilds every visible chip whenever it
|
||||
/// changes on *any* tab. High-churn fields (load progress, thumbnails, session
|
||||
/// history, find-in-page results, translation) live in per-tab notifiers in
|
||||
/// `providers/tab_detail_state.dart` instead.
|
||||
@CopyWith(constructor: '_')
|
||||
class TabState extends WebPageInfo {
|
||||
static final defaultUrl = Uri.parse('about:blank');
|
||||
@@ -59,10 +64,6 @@ class TabState extends WebPageInfo {
|
||||
),
|
||||
);
|
||||
|
||||
final EquatableImage? thumbnail;
|
||||
|
||||
final int progress;
|
||||
|
||||
final TabMode tabMode;
|
||||
String? get isolationContextId => tabMode.isolationContextId;
|
||||
|
||||
@@ -70,13 +71,8 @@ class TabState extends WebPageInfo {
|
||||
final bool isLoading;
|
||||
final bool showToolbarAsExpanded;
|
||||
|
||||
bool get isFinishedLoading => !isLoading && progress == 100;
|
||||
|
||||
final SecurityState securityInfoState;
|
||||
final HistoryState historyState;
|
||||
final ReaderableState readerableState;
|
||||
final FindResultState findResultState;
|
||||
final TranslationState translationState;
|
||||
|
||||
TabState({
|
||||
required this.id,
|
||||
@@ -85,17 +81,12 @@ class TabState extends WebPageInfo {
|
||||
required super.url,
|
||||
required String title,
|
||||
required this.icon,
|
||||
required this.thumbnail,
|
||||
required this.progress,
|
||||
this.tabMode = TabMode.regular,
|
||||
required this.isFullScreen,
|
||||
required this.isLoading,
|
||||
required this.showToolbarAsExpanded,
|
||||
required this.securityInfoState,
|
||||
required this.historyState,
|
||||
required this.readerableState,
|
||||
required this.findResultState,
|
||||
required this.translationState,
|
||||
}) : super(title: title.trim());
|
||||
|
||||
TabState._({
|
||||
@@ -105,17 +96,12 @@ class TabState extends WebPageInfo {
|
||||
required super.url,
|
||||
required super.title,
|
||||
required this.icon,
|
||||
required this.thumbnail,
|
||||
required this.progress,
|
||||
required this.tabMode,
|
||||
required this.isFullScreen,
|
||||
required this.isLoading,
|
||||
required this.showToolbarAsExpanded,
|
||||
required this.securityInfoState,
|
||||
required this.historyState,
|
||||
required this.readerableState,
|
||||
required this.findResultState,
|
||||
required this.translationState,
|
||||
});
|
||||
|
||||
factory TabState.$default(String tabId) => TabState(
|
||||
@@ -125,16 +111,11 @@ class TabState extends WebPageInfo {
|
||||
url: defaultUrl,
|
||||
title: "",
|
||||
icon: null,
|
||||
thumbnail: null,
|
||||
progress: 0,
|
||||
isFullScreen: false,
|
||||
isLoading: false,
|
||||
showToolbarAsExpanded: false,
|
||||
securityInfoState: SecurityState.$default(),
|
||||
historyState: HistoryState.$default(),
|
||||
readerableState: ReaderableState.$default(),
|
||||
findResultState: FindResultState.$default(),
|
||||
translationState: TranslationState.$default(),
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -144,16 +125,11 @@ class TabState extends WebPageInfo {
|
||||
parentId,
|
||||
contextId,
|
||||
icon,
|
||||
thumbnail,
|
||||
progress,
|
||||
tabMode,
|
||||
isFullScreen,
|
||||
isLoading,
|
||||
showToolbarAsExpanded,
|
||||
securityInfoState,
|
||||
historyState,
|
||||
readerableState,
|
||||
findResultState,
|
||||
translationState,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,10 +17,6 @@ abstract class _$TabStateCWProxy {
|
||||
|
||||
TabState icon(EquatableImage? icon);
|
||||
|
||||
TabState thumbnail(EquatableImage? thumbnail);
|
||||
|
||||
TabState progress(int progress);
|
||||
|
||||
TabState tabMode(TabMode tabMode);
|
||||
|
||||
TabState isFullScreen(bool isFullScreen);
|
||||
@@ -31,14 +27,8 @@ abstract class _$TabStateCWProxy {
|
||||
|
||||
TabState securityInfoState(SecurityState securityInfoState);
|
||||
|
||||
TabState historyState(HistoryState historyState);
|
||||
|
||||
TabState readerableState(ReaderableState readerableState);
|
||||
|
||||
TabState findResultState(FindResultState findResultState);
|
||||
|
||||
TabState translationState(TranslationState translationState);
|
||||
|
||||
/// Creates a new instance with the provided field values.
|
||||
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `TabState(...).copyWith.fieldName(value)`.
|
||||
///
|
||||
@@ -52,17 +42,12 @@ abstract class _$TabStateCWProxy {
|
||||
Uri url,
|
||||
String? title,
|
||||
EquatableImage? icon,
|
||||
EquatableImage? thumbnail,
|
||||
int progress,
|
||||
TabMode tabMode,
|
||||
bool isFullScreen,
|
||||
bool isLoading,
|
||||
bool showToolbarAsExpanded,
|
||||
SecurityState securityInfoState,
|
||||
HistoryState historyState,
|
||||
ReaderableState readerableState,
|
||||
FindResultState findResultState,
|
||||
TranslationState translationState,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,12 +73,6 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
||||
@override
|
||||
TabState icon(EquatableImage? icon) => call(icon: icon);
|
||||
|
||||
@override
|
||||
TabState thumbnail(EquatableImage? thumbnail) => call(thumbnail: thumbnail);
|
||||
|
||||
@override
|
||||
TabState progress(int progress) => call(progress: progress);
|
||||
|
||||
@override
|
||||
TabState tabMode(TabMode tabMode) => call(tabMode: tabMode);
|
||||
|
||||
@@ -111,22 +90,10 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
||||
TabState securityInfoState(SecurityState securityInfoState) =>
|
||||
call(securityInfoState: securityInfoState);
|
||||
|
||||
@override
|
||||
TabState historyState(HistoryState historyState) =>
|
||||
call(historyState: historyState);
|
||||
|
||||
@override
|
||||
TabState readerableState(ReaderableState readerableState) =>
|
||||
call(readerableState: readerableState);
|
||||
|
||||
@override
|
||||
TabState findResultState(FindResultState findResultState) =>
|
||||
call(findResultState: findResultState);
|
||||
|
||||
@override
|
||||
TabState translationState(TranslationState translationState) =>
|
||||
call(translationState: translationState);
|
||||
|
||||
@override
|
||||
/// Creates a new instance with the provided field values.
|
||||
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `TabState(...).copyWith.fieldName(value)`.
|
||||
@@ -141,17 +108,12 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
||||
Object? url = const $CopyWithPlaceholder(),
|
||||
Object? title = const $CopyWithPlaceholder(),
|
||||
Object? icon = const $CopyWithPlaceholder(),
|
||||
Object? thumbnail = const $CopyWithPlaceholder(),
|
||||
Object? progress = const $CopyWithPlaceholder(),
|
||||
Object? tabMode = const $CopyWithPlaceholder(),
|
||||
Object? isFullScreen = const $CopyWithPlaceholder(),
|
||||
Object? isLoading = const $CopyWithPlaceholder(),
|
||||
Object? showToolbarAsExpanded = const $CopyWithPlaceholder(),
|
||||
Object? securityInfoState = const $CopyWithPlaceholder(),
|
||||
Object? historyState = const $CopyWithPlaceholder(),
|
||||
Object? readerableState = const $CopyWithPlaceholder(),
|
||||
Object? findResultState = const $CopyWithPlaceholder(),
|
||||
Object? translationState = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return TabState._(
|
||||
id: _value.id,
|
||||
@@ -175,14 +137,6 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
||||
? _value.icon
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: icon as EquatableImage?,
|
||||
thumbnail: thumbnail == const $CopyWithPlaceholder()
|
||||
? _value.thumbnail
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: thumbnail as EquatableImage?,
|
||||
progress: progress == const $CopyWithPlaceholder() || progress == null
|
||||
? _value.progress
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: progress as int,
|
||||
tabMode: tabMode == const $CopyWithPlaceholder() || tabMode == null
|
||||
? _value.tabMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
@@ -208,29 +162,12 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
||||
? _value.securityInfoState
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: securityInfoState as SecurityState,
|
||||
historyState:
|
||||
historyState == const $CopyWithPlaceholder() || historyState == null
|
||||
? _value.historyState
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: historyState as HistoryState,
|
||||
readerableState:
|
||||
readerableState == const $CopyWithPlaceholder() ||
|
||||
readerableState == null
|
||||
? _value.readerableState
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: readerableState as ReaderableState,
|
||||
findResultState:
|
||||
findResultState == const $CopyWithPlaceholder() ||
|
||||
findResultState == null
|
||||
? _value.findResultState
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: findResultState as FindResultState,
|
||||
translationState:
|
||||
translationState == const $CopyWithPlaceholder() ||
|
||||
translationState == null
|
||||
? _value.translationState
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: translationState as TranslationState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/// High-churn per-tab state, deliberately kept *out* of [TabState].
|
||||
///
|
||||
/// [TabState] is fanned out to the always-visible browser chrome — the quick
|
||||
/// tab switcher chips, the grouped tab list, the contextual toolbar — through
|
||||
/// providers that watch the whole `Map<String, TabState>`. Any field that ticks
|
||||
/// during a page load or on a background timer therefore rebuilt every chip on
|
||||
/// screen, even though no chip renders that field.
|
||||
///
|
||||
/// The fields here are each consumed by exactly one or two widgets, so they
|
||||
/// live in their own keyed notifiers and are read through per-tab selectors.
|
||||
/// A progress tick or a background tab's screenshot now invalidates only the
|
||||
/// widget that actually shows it.
|
||||
library;
|
||||
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/domain/entities/equatable_image.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/translation.dart';
|
||||
|
||||
part 'tab_detail_state.g.dart';
|
||||
|
||||
/// Load progress (0-100) per tab. Ticks continuously while a page loads.
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabProgressStates extends _$TabProgressStates {
|
||||
@override
|
||||
Map<String, int> build() => const {};
|
||||
|
||||
void update(String tabId, int progress) {
|
||||
if (state[tabId] == progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..[tabId] = progress;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
int tabProgress(Ref ref, String? tabId) {
|
||||
if (tabId == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ref.watch(tabProgressStatesProvider.select((s) => s[tabId] ?? 0));
|
||||
}
|
||||
|
||||
/// Page screenshots per tab. Refreshed on a 10s timer for the selected tab and
|
||||
/// consumed only by the tab tray previews.
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabThumbnails extends _$TabThumbnails {
|
||||
@override
|
||||
Map<String, EquatableImage> build() => const {};
|
||||
|
||||
void update(String tabId, EquatableImage? thumbnail) {
|
||||
if (thumbnail == null) {
|
||||
if (!state.containsKey(tabId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..remove(tabId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state[tabId] == thumbnail) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..[tabId] = thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
EquatableImage? tabThumbnail(Ref ref, String? tabId) {
|
||||
if (tabId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ref.watch(tabThumbnailsProvider.select((s) => s[tabId]));
|
||||
}
|
||||
|
||||
/// Session history (back/forward stack) per tab.
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabHistoryStates extends _$TabHistoryStates {
|
||||
@override
|
||||
Map<String, HistoryState> build() => const {};
|
||||
|
||||
void update(String tabId, HistoryState history) {
|
||||
if (state[tabId] == history) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..[tabId] = history;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
HistoryState tabHistoryState(Ref ref, String? tabId) {
|
||||
if (tabId == null) {
|
||||
return HistoryState.$default();
|
||||
}
|
||||
|
||||
return ref.watch(
|
||||
tabHistoryStatesProvider.select((s) => s[tabId] ?? HistoryState.$default()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Find-in-page match counters per tab. Emitted at a high rate by Gecko while
|
||||
/// a search is running.
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabFindResultStates extends _$TabFindResultStates {
|
||||
@override
|
||||
Map<String, FindResultState> build() => const {};
|
||||
|
||||
void update(String tabId, FindResultState result) {
|
||||
if (state[tabId] == result) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..[tabId] = result;
|
||||
}
|
||||
|
||||
FindResultState resultFor(String tabId) =>
|
||||
state[tabId] ?? FindResultState.$default();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
FindResultState tabFindResultState(Ref ref, String? tabId) {
|
||||
if (tabId == null) {
|
||||
return FindResultState.$default();
|
||||
}
|
||||
|
||||
return ref.watch(
|
||||
tabFindResultStatesProvider.select(
|
||||
(s) => s[tabId] ?? FindResultState.$default(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Translation progress/result per tab.
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabTranslationStates extends _$TabTranslationStates {
|
||||
@override
|
||||
Map<String, TranslationState> build() => const {};
|
||||
|
||||
void update(String tabId, TranslationState translation) {
|
||||
if (state[tabId] == translation) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..[tabId] = translation;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
TranslationState tabTranslationState(Ref ref, String? tabId) {
|
||||
if (tabId == null) {
|
||||
return TranslationState.$default();
|
||||
}
|
||||
|
||||
return ref.watch(
|
||||
tabTranslationStatesProvider.select(
|
||||
(s) => s[tabId] ?? TranslationState.$default(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'tab_detail_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Load progress (0-100) per tab. Ticks continuously while a page loads.
|
||||
|
||||
@ProviderFor(TabProgressStates)
|
||||
final tabProgressStatesProvider = TabProgressStatesProvider._();
|
||||
|
||||
/// Load progress (0-100) per tab. Ticks continuously while a page loads.
|
||||
final class TabProgressStatesProvider
|
||||
extends $NotifierProvider<TabProgressStates, Map<String, int>> {
|
||||
/// Load progress (0-100) per tab. Ticks continuously while a page loads.
|
||||
TabProgressStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tabProgressStatesProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabProgressStatesHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TabProgressStates create() => TabProgressStates();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Map<String, int> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Map<String, int>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabProgressStatesHash() => r'60bb03e33e4322eb2869b90b5cc5802d3cdc225d';
|
||||
|
||||
/// Load progress (0-100) per tab. Ticks continuously while a page loads.
|
||||
|
||||
abstract class _$TabProgressStates extends $Notifier<Map<String, int>> {
|
||||
Map<String, int> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
WhenComplete runBuild() {
|
||||
final ref = this.ref as $Ref<Map<String, int>, Map<String, int>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<Map<String, int>, Map<String, int>>,
|
||||
Map<String, int>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
return element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(tabProgress)
|
||||
final tabProgressProvider = TabProgressFamily._();
|
||||
|
||||
final class TabProgressProvider extends $FunctionalProvider<int, int, int>
|
||||
with $Provider<int> {
|
||||
TabProgressProvider._({
|
||||
required TabProgressFamily super.from,
|
||||
required String? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'tabProgressProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabProgressHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'tabProgressProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<int> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
int create(Ref ref) {
|
||||
final argument = this.argument as String?;
|
||||
return tabProgress(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(int value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<int>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabProgressProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabProgressHash() => r'83c4990d96f2160e919ee99882905c09872e8bb7';
|
||||
|
||||
final class TabProgressFamily extends $Family
|
||||
with $FunctionalFamilyOverride<int, String?> {
|
||||
TabProgressFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'tabProgressProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
TabProgressProvider call(String? tabId) =>
|
||||
TabProgressProvider._(argument: tabId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'tabProgressProvider';
|
||||
}
|
||||
|
||||
/// Page screenshots per tab. Refreshed on a 10s timer for the selected tab and
|
||||
/// consumed only by the tab tray previews.
|
||||
|
||||
@ProviderFor(TabThumbnails)
|
||||
final tabThumbnailsProvider = TabThumbnailsProvider._();
|
||||
|
||||
/// Page screenshots per tab. Refreshed on a 10s timer for the selected tab and
|
||||
/// consumed only by the tab tray previews.
|
||||
final class TabThumbnailsProvider
|
||||
extends $NotifierProvider<TabThumbnails, Map<String, EquatableImage>> {
|
||||
/// Page screenshots per tab. Refreshed on a 10s timer for the selected tab and
|
||||
/// consumed only by the tab tray previews.
|
||||
TabThumbnailsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tabThumbnailsProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabThumbnailsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TabThumbnails create() => TabThumbnails();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Map<String, EquatableImage> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Map<String, EquatableImage>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabThumbnailsHash() => r'16c4bb6edff2e6413100d9ce02c1ae7948740cfe';
|
||||
|
||||
/// Page screenshots per tab. Refreshed on a 10s timer for the selected tab and
|
||||
/// consumed only by the tab tray previews.
|
||||
|
||||
abstract class _$TabThumbnails extends $Notifier<Map<String, EquatableImage>> {
|
||||
Map<String, EquatableImage> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
WhenComplete runBuild() {
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<Map<String, EquatableImage>, Map<String, EquatableImage>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<
|
||||
Map<String, EquatableImage>,
|
||||
Map<String, EquatableImage>
|
||||
>,
|
||||
Map<String, EquatableImage>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
return element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(tabThumbnail)
|
||||
final tabThumbnailProvider = TabThumbnailFamily._();
|
||||
|
||||
final class TabThumbnailProvider
|
||||
extends
|
||||
$FunctionalProvider<EquatableImage?, EquatableImage?, EquatableImage?>
|
||||
with $Provider<EquatableImage?> {
|
||||
TabThumbnailProvider._({
|
||||
required TabThumbnailFamily super.from,
|
||||
required String? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'tabThumbnailProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabThumbnailHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'tabThumbnailProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableImage?> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableImage? create(Ref ref) {
|
||||
final argument = this.argument as String?;
|
||||
return tabThumbnail(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(EquatableImage? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<EquatableImage?>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabThumbnailProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabThumbnailHash() => r'7c8b591abdb5e8cc914ab8e44247a4bca6bddd15';
|
||||
|
||||
final class TabThumbnailFamily extends $Family
|
||||
with $FunctionalFamilyOverride<EquatableImage?, String?> {
|
||||
TabThumbnailFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'tabThumbnailProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
TabThumbnailProvider call(String? tabId) =>
|
||||
TabThumbnailProvider._(argument: tabId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'tabThumbnailProvider';
|
||||
}
|
||||
|
||||
/// Session history (back/forward stack) per tab.
|
||||
|
||||
@ProviderFor(TabHistoryStates)
|
||||
final tabHistoryStatesProvider = TabHistoryStatesProvider._();
|
||||
|
||||
/// Session history (back/forward stack) per tab.
|
||||
final class TabHistoryStatesProvider
|
||||
extends $NotifierProvider<TabHistoryStates, Map<String, HistoryState>> {
|
||||
/// Session history (back/forward stack) per tab.
|
||||
TabHistoryStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tabHistoryStatesProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabHistoryStatesHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TabHistoryStates create() => TabHistoryStates();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Map<String, HistoryState> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Map<String, HistoryState>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabHistoryStatesHash() => r'3920410334c4354ca66da605d9bfe85b4853f3bf';
|
||||
|
||||
/// Session history (back/forward stack) per tab.
|
||||
|
||||
abstract class _$TabHistoryStates extends $Notifier<Map<String, HistoryState>> {
|
||||
Map<String, HistoryState> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
WhenComplete runBuild() {
|
||||
final ref =
|
||||
this.ref as $Ref<Map<String, HistoryState>, Map<String, HistoryState>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<Map<String, HistoryState>, Map<String, HistoryState>>,
|
||||
Map<String, HistoryState>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
return element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(tabHistoryState)
|
||||
final tabHistoryStateProvider = TabHistoryStateFamily._();
|
||||
|
||||
final class TabHistoryStateProvider
|
||||
extends $FunctionalProvider<HistoryState, HistoryState, HistoryState>
|
||||
with $Provider<HistoryState> {
|
||||
TabHistoryStateProvider._({
|
||||
required TabHistoryStateFamily super.from,
|
||||
required String? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'tabHistoryStateProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabHistoryStateHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'tabHistoryStateProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<HistoryState> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
HistoryState create(Ref ref) {
|
||||
final argument = this.argument as String?;
|
||||
return tabHistoryState(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(HistoryState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<HistoryState>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabHistoryStateProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabHistoryStateHash() => r'5bca69fddb6fc2f3db6caf5c84bd1a7f5728e1d9';
|
||||
|
||||
final class TabHistoryStateFamily extends $Family
|
||||
with $FunctionalFamilyOverride<HistoryState, String?> {
|
||||
TabHistoryStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'tabHistoryStateProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
TabHistoryStateProvider call(String? tabId) =>
|
||||
TabHistoryStateProvider._(argument: tabId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'tabHistoryStateProvider';
|
||||
}
|
||||
|
||||
/// Find-in-page match counters per tab. Emitted at a high rate by Gecko while
|
||||
/// a search is running.
|
||||
|
||||
@ProviderFor(TabFindResultStates)
|
||||
final tabFindResultStatesProvider = TabFindResultStatesProvider._();
|
||||
|
||||
/// Find-in-page match counters per tab. Emitted at a high rate by Gecko while
|
||||
/// a search is running.
|
||||
final class TabFindResultStatesProvider
|
||||
extends
|
||||
$NotifierProvider<TabFindResultStates, Map<String, FindResultState>> {
|
||||
/// Find-in-page match counters per tab. Emitted at a high rate by Gecko while
|
||||
/// a search is running.
|
||||
TabFindResultStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tabFindResultStatesProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabFindResultStatesHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TabFindResultStates create() => TabFindResultStates();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Map<String, FindResultState> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Map<String, FindResultState>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabFindResultStatesHash() =>
|
||||
r'f57dee1658eae789002ac3cff15808cbc96b7883';
|
||||
|
||||
/// Find-in-page match counters per tab. Emitted at a high rate by Gecko while
|
||||
/// a search is running.
|
||||
|
||||
abstract class _$TabFindResultStates
|
||||
extends $Notifier<Map<String, FindResultState>> {
|
||||
Map<String, FindResultState> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
WhenComplete runBuild() {
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<Map<String, FindResultState>, Map<String, FindResultState>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<
|
||||
Map<String, FindResultState>,
|
||||
Map<String, FindResultState>
|
||||
>,
|
||||
Map<String, FindResultState>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
return element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(tabFindResultState)
|
||||
final tabFindResultStateProvider = TabFindResultStateFamily._();
|
||||
|
||||
final class TabFindResultStateProvider
|
||||
extends
|
||||
$FunctionalProvider<FindResultState, FindResultState, FindResultState>
|
||||
with $Provider<FindResultState> {
|
||||
TabFindResultStateProvider._({
|
||||
required TabFindResultStateFamily super.from,
|
||||
required String? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'tabFindResultStateProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabFindResultStateHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'tabFindResultStateProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<FindResultState> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FindResultState create(Ref ref) {
|
||||
final argument = this.argument as String?;
|
||||
return tabFindResultState(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(FindResultState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<FindResultState>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabFindResultStateProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabFindResultStateHash() =>
|
||||
r'0f58040111bb43d830e14ae44569265b9ae804d9';
|
||||
|
||||
final class TabFindResultStateFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FindResultState, String?> {
|
||||
TabFindResultStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'tabFindResultStateProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
TabFindResultStateProvider call(String? tabId) =>
|
||||
TabFindResultStateProvider._(argument: tabId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'tabFindResultStateProvider';
|
||||
}
|
||||
|
||||
/// Translation progress/result per tab.
|
||||
|
||||
@ProviderFor(TabTranslationStates)
|
||||
final tabTranslationStatesProvider = TabTranslationStatesProvider._();
|
||||
|
||||
/// Translation progress/result per tab.
|
||||
final class TabTranslationStatesProvider
|
||||
extends
|
||||
$NotifierProvider<TabTranslationStates, Map<String, TranslationState>> {
|
||||
/// Translation progress/result per tab.
|
||||
TabTranslationStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tabTranslationStatesProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabTranslationStatesHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TabTranslationStates create() => TabTranslationStates();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Map<String, TranslationState> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Map<String, TranslationState>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabTranslationStatesHash() =>
|
||||
r'f04004026dc5fd638319528a55e2f10e0f64c9b3';
|
||||
|
||||
/// Translation progress/result per tab.
|
||||
|
||||
abstract class _$TabTranslationStates
|
||||
extends $Notifier<Map<String, TranslationState>> {
|
||||
Map<String, TranslationState> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
WhenComplete runBuild() {
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<
|
||||
Map<String, TranslationState>,
|
||||
Map<String, TranslationState>
|
||||
>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<
|
||||
Map<String, TranslationState>,
|
||||
Map<String, TranslationState>
|
||||
>,
|
||||
Map<String, TranslationState>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
return element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(tabTranslationState)
|
||||
final tabTranslationStateProvider = TabTranslationStateFamily._();
|
||||
|
||||
final class TabTranslationStateProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
TranslationState,
|
||||
TranslationState,
|
||||
TranslationState
|
||||
>
|
||||
with $Provider<TranslationState> {
|
||||
TabTranslationStateProvider._({
|
||||
required TabTranslationStateFamily super.from,
|
||||
required String? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'tabTranslationStateProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabTranslationStateHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'tabTranslationStateProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<TranslationState> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
TranslationState create(Ref ref) {
|
||||
final argument = this.argument as String?;
|
||||
return tabTranslationState(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(TranslationState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<TranslationState>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabTranslationStateProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabTranslationStateHash() =>
|
||||
r'886365e36167542b2e4dbdbeb97c942a414a752e';
|
||||
|
||||
final class TabTranslationStateFamily extends $Family
|
||||
with $FunctionalFamilyOverride<TranslationState, String?> {
|
||||
TabTranslationStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'tabTranslationStateProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
TabTranslationStateProvider call(String? tabId) =>
|
||||
TabTranslationStateProvider._(argument: tabId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'tabTranslationStateProvider';
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:nullability/nullability.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
@@ -34,6 +35,7 @@ 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/domain/providers/tab_detail_state.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';
|
||||
@@ -46,8 +48,33 @@ import 'package:weblibre/features/user/domain/repositories/proxy_routing_setting
|
||||
|
||||
part 'tab_state.g.dart';
|
||||
|
||||
/// Decode width for tab thumbnails. The screenshots arrive at full device
|
||||
/// resolution but are only ever shown in tab-tray previews a few hundred
|
||||
/// logical pixels wide, so decoding them at native size burns main-isolate
|
||||
/// time and GPU memory for detail nobody sees. Only the width is constrained
|
||||
/// so the decoder keeps the aspect ratio; upscaling is disabled so smaller
|
||||
/// sources are left alone.
|
||||
const thumbnailDecodeWidth = 720;
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabStates extends _$TabStates {
|
||||
/// Replaces the entry for [tabId] — but only when [next] actually differs.
|
||||
///
|
||||
/// Every write here allocates a new map, and `Map` compares by identity, so
|
||||
/// an unconditional assignment notifies *all* whole-map watchers (the quick
|
||||
/// tab switcher, the grouped tab list, the FIFO list) even when nothing
|
||||
/// changed. Gecko re-emits the full content state on every progress tick, so
|
||||
/// without this guard a single page load pushed dozens of no-op rebuilds
|
||||
/// through the entire browser chrome. [TabState] is `FastEquatable`, so the
|
||||
/// comparison is a cached-hash check.
|
||||
void _put(String tabId, TabState next) {
|
||||
if (state[tabId] == next) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {...state}..[tabId] = next;
|
||||
}
|
||||
|
||||
Future<void> _onTabContentStateChange(TabContentState contentState) async {
|
||||
final current = await patchedState(contentState.id);
|
||||
|
||||
@@ -82,14 +109,19 @@ class TabStates extends _$TabStates {
|
||||
contextId: contentState.contextId,
|
||||
url: url,
|
||||
title: resolvedTitle,
|
||||
progress: contentState.progress,
|
||||
tabMode: inferredTabMode,
|
||||
isFullScreen: contentState.isFullScreen,
|
||||
isLoading: contentState.isLoading,
|
||||
showToolbarAsExpanded: contentState.showToolbarAsExpanded,
|
||||
);
|
||||
|
||||
state = {...state}..[contentState.id] = newState;
|
||||
_put(contentState.id, newState);
|
||||
|
||||
// Progress lives outside [TabState] so a load tick doesn't invalidate the
|
||||
// whole map (and with it every chip in the quick tab switcher).
|
||||
ref
|
||||
.read(tabProgressStatesProvider.notifier)
|
||||
.update(contentState.id, contentState.progress);
|
||||
|
||||
// Only reconcile DB hierarchy when the engine parent link actually changes.
|
||||
// Content-state events also fire on every progress/title tick, and seeding
|
||||
@@ -106,7 +138,7 @@ class TabStates extends _$TabStates {
|
||||
);
|
||||
}
|
||||
|
||||
if (newState.isFinishedLoading) {
|
||||
if (!contentState.isLoading && contentState.progress == 100) {
|
||||
ref
|
||||
.read(geckoInferenceRepositoryProvider.notifier)
|
||||
.markInitialLoadComplete();
|
||||
@@ -138,97 +170,111 @@ class TabStates extends _$TabStates {
|
||||
final IconChangeEvent(:tabId, :bytes) = event;
|
||||
|
||||
final image = await bytes.mapNotNull((bytes) => tryDecodeImage(bytes));
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
|
||||
state = {...state}..[tabId] = current.copyWith.icon(image);
|
||||
if (!ref.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
_put(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);
|
||||
final image = await bytes.mapNotNull(
|
||||
(bytes) => tryDecodeImage(
|
||||
bytes,
|
||||
targetWidth: thumbnailDecodeWidth,
|
||||
allowUpscaling: false,
|
||||
),
|
||||
);
|
||||
|
||||
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
|
||||
if (!ref.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(tabThumbnailsProvider.notifier).update(tabId, image);
|
||||
}
|
||||
|
||||
void _onSecurityInfoStateChange(SecurityInfoEvent event) {
|
||||
final SecurityInfoEvent(:tabId, :securityInfo) = event;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
state = {...state}
|
||||
..[tabId] = current.copyWith.securityInfoState(
|
||||
_put(
|
||||
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,
|
||||
),
|
||||
);
|
||||
ref
|
||||
.read(tabHistoryStatesProvider.notifier)
|
||||
.update(
|
||||
tabId,
|
||||
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(
|
||||
_put(
|
||||
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),
|
||||
);
|
||||
ref
|
||||
.read(tabTranslationStatesProvider.notifier)
|
||||
.update(tabId, TranslationState.fromData(state));
|
||||
}
|
||||
|
||||
void _onFindResultsChange(FindResultsEvent event) {
|
||||
final FindResultsEvent(:tabId, :results) = event;
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
final findResults = ref.read(tabFindResultStatesProvider.notifier);
|
||||
|
||||
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());
|
||||
findResults.update(
|
||||
tabId,
|
||||
FindResultState(
|
||||
lastSearchText: ref.read(findInPageRepositoryProvider(tabId)),
|
||||
activeMatchOrdinal: result.activeMatchOrdinal,
|
||||
numberOfMatches: result.numberOfMatches,
|
||||
isDoneCounting: result.isDoneCounting,
|
||||
),
|
||||
);
|
||||
} else if (findResults.resultFor(tabId).hasMatches) {
|
||||
findResults.update(tabId, FindResultState.$default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,8 +355,15 @@ class TabStates extends _$TabStates {
|
||||
);
|
||||
},
|
||||
),
|
||||
// Gecko emits match counters at ~40/s while a find is running. 25ms let
|
||||
// essentially every one of those through; 100ms is still well under the
|
||||
// threshold where the counter feels laggy. Debounced *per tab* so a
|
||||
// background tab still counting can't starve the foreground one.
|
||||
eventService.findResultsEvent
|
||||
.debounceTime(const Duration(milliseconds: 25))
|
||||
.groupBy((event) => event.tabId)
|
||||
.flatMap(
|
||||
(group) => group.debounceTime(const Duration(milliseconds: 100)),
|
||||
)
|
||||
.listen(
|
||||
(event) {
|
||||
_onFindResultsChange(event);
|
||||
@@ -381,6 +434,42 @@ TabState? tabState(Ref ref, String? tabId) {
|
||||
return ref.watch(tabStatesProvider.select((tabs) => tabs[tabId]));
|
||||
}
|
||||
|
||||
/// The only fields of [TabState] that tab filtering/sorting depends on.
|
||||
class TabSortKeys with FastEquatable {
|
||||
final TabMode tabMode;
|
||||
final String titleOrAuthority;
|
||||
final String url;
|
||||
|
||||
TabSortKeys({
|
||||
required this.tabMode,
|
||||
required this.titleOrAuthority,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [tabMode, titleOrAuthority, url];
|
||||
}
|
||||
|
||||
/// Projection of [tabStatesProvider] for consumers that order or filter tabs
|
||||
/// but render nothing from the state itself. Watching this instead of the full
|
||||
/// map keeps the (expensive) grouping/sorting passes off the path of every
|
||||
/// icon, security-info and readerable event.
|
||||
@Riverpod(keepAlive: true)
|
||||
EquatableValue<Map<String, TabSortKeys>> tabSortKeys(Ref ref) {
|
||||
return ref.watch(
|
||||
tabStatesProvider.select(
|
||||
(states) => EquatableValue({
|
||||
for (final MapEntry(:key, :value) in states.entries)
|
||||
key: TabSortKeys(
|
||||
tabMode: value.tabMode,
|
||||
titleOrAuthority: value.titleOrAuthority,
|
||||
url: value.url.toString(),
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Future<TabState> tabStateWithFallback(Ref ref, String tabId) async {
|
||||
final state = ref.watch(tabStateProvider(tabId));
|
||||
|
||||
@@ -41,7 +41,7 @@ final class TabStatesProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabStatesHash() => r'04b7f1ea704575e368a0f1186fc1c58f317620b1';
|
||||
String _$tabStatesHash() => r'1ae4e941890116eabf1803809014b3e575c33e73';
|
||||
|
||||
abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
|
||||
Map<String, TabState> build();
|
||||
@@ -138,6 +138,68 @@ final class TabStateFamily extends $Family
|
||||
String toString() => r'tabStateProvider';
|
||||
}
|
||||
|
||||
/// Projection of [tabStatesProvider] for consumers that order or filter tabs
|
||||
/// but render nothing from the state itself. Watching this instead of the full
|
||||
/// map keeps the (expensive) grouping/sorting passes off the path of every
|
||||
/// icon, security-info and readerable event.
|
||||
|
||||
@ProviderFor(tabSortKeys)
|
||||
final tabSortKeysProvider = TabSortKeysProvider._();
|
||||
|
||||
/// Projection of [tabStatesProvider] for consumers that order or filter tabs
|
||||
/// but render nothing from the state itself. Watching this instead of the full
|
||||
/// map keeps the (expensive) grouping/sorting passes off the path of every
|
||||
/// icon, security-info and readerable event.
|
||||
|
||||
final class TabSortKeysProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
EquatableValue<Map<String, TabSortKeys>>,
|
||||
EquatableValue<Map<String, TabSortKeys>>,
|
||||
EquatableValue<Map<String, TabSortKeys>>
|
||||
>
|
||||
with $Provider<EquatableValue<Map<String, TabSortKeys>>> {
|
||||
/// Projection of [tabStatesProvider] for consumers that order or filter tabs
|
||||
/// but render nothing from the state itself. Watching this instead of the full
|
||||
/// map keeps the (expensive) grouping/sorting passes off the path of every
|
||||
/// icon, security-info and readerable event.
|
||||
TabSortKeysProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tabSortKeysProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabSortKeysHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<EquatableValue<Map<String, TabSortKeys>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
EquatableValue<Map<String, TabSortKeys>> create(Ref ref) {
|
||||
return tabSortKeys(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(EquatableValue<Map<String, TabSortKeys>> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride:
|
||||
$SyncValueProvider<EquatableValue<Map<String, TabSortKeys>>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabSortKeysHash() => r'7bf660099d006b6df9846594d2a5d78a32fa3bdb';
|
||||
|
||||
@ProviderFor(tabStateWithFallback)
|
||||
final tabStateWithFallbackProvider = TabStateWithFallbackFamily._();
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/pending_tab_selection.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/restore_complete.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_detail_state.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.dart';
|
||||
@@ -957,9 +958,15 @@ class TabRepository extends _$TabRepository {
|
||||
return;
|
||||
}
|
||||
|
||||
final historyIsEmpty =
|
||||
ref
|
||||
.read(tabHistoryStatesProvider)[currentTabState.id]
|
||||
?.items
|
||||
.isEmpty ??
|
||||
true;
|
||||
|
||||
final tabIsEmpty =
|
||||
currentTabState.url == TabState.defaultUrl &&
|
||||
currentTabState.historyState.items.isEmpty;
|
||||
currentTabState.url == TabState.defaultUrl && historyIsEmpty;
|
||||
|
||||
if (event.blocked || tabIsEmpty) {
|
||||
final newTabId = await addTab(
|
||||
@@ -976,7 +983,7 @@ class TabRepository extends _$TabRepository {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTabState.historyState.items.isEmpty) {
|
||||
if (historyIsEmpty) {
|
||||
await closeTab(currentTabState.id);
|
||||
if (!ref.mounted) {
|
||||
return;
|
||||
|
||||
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabRepositoryHash() => r'e80c972b0d1a328c170354c123d2730028a774b2';
|
||||
String _$tabRepositoryHash() => r'8abe7686d937434e2970675c143f3891ec34cce2';
|
||||
|
||||
abstract class _$TabRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
Reference in New Issue
Block a user